MOVE SEMANTICS
MITSUTAKA TAKEDA
MITSUTAKA.TAKEDA@GMAIL.COM
TABLE OF CONTENTS
1. C++ for Java/Scala
1.1. Copy Semantics(Value Semantics)
1.2. Copy Semantics(Value Semantics)
1.3. 寄り道(Reference/Pointer Semantics)
1.4. Where is My Copy?
1.5. Copyの現実
2. Move Semantics
2.1. Move!
2.2. 寄り道(リソースの所有権)
3. L/R Value
4. L/R Value Reference
4.1. 注意点(L/R Value と L/R Value Reference)
5. Non Copyiable Object(Movable Object)
6. Move Semantics導入前後
6.1. Move Semantics導入前
6.2. Move Semantics導入後
1 C++ FOR JAVA/SCALA
1.1 COPY SEMANTICS(VALUE SEMANTICS)
C++ではオブジェクトは基本的に組み込み型のように振舞う(Value
Semantics)。 Javaで言うところのValue Object(int, String, etc)。
int x = 0;
int y = x;
y = 10; // xの値は 0 か 10のどちらでしょうか?
1.2 COPY SEMANTICS(VALUE SEMANTICS)
コピーとは等価の別オブジェクト(コピー先への変更はコピー元に
影響しない)を作ること。
int x = 0;
int y = x; // xをyへコピー
assert(x == y); // コピー直後はxとyは等価。
y = 10; // yを変更。
assert(x == 0); // y(コピー先)への変更はx(コピー元)へ影響しない。
assert(y == 10);
1.3 寄り道(REFERENCE/POINTER SEMANTICS)
Reference/Pointerセマンティックスも利用するこができます。
int main() {
int x = 0;
int& y = x; // yはxへの参照。
y = 10;
assert(x == y);
assert(x == 10);
assert(y == 10);
int* z = &x; // zはxへのポインタ。
*z = 20; // ポインタの指す先(x)を変更。
assert(x == y); // yはxへの参照なのでxが変更されればyも変わる。
assert(x == 20);
assert(y == 20);
assert(*z == 20);
return 0;
}
1.4 WHERE IS MY COPY?
何回コピーするでしょう?
class MyClass {
public:
MyClass() = default;
MyClass(const MyClass&) {
std::cout << "I'm Copied!n";
}
};
MyClass f(MyClass p0,
const MyClass& p1) {
return p0;
}
int main() {
MyClass m;
MyClass n = f(m, m);
return 0;
}
1.5 COPYの現実
compilerが最適化してくれる場合もあり。
Return Value Optimization(Named Return Value Optimization)
MyClass f() {
return MyClass();
}
void f_optimized(MyClass* out){
return *out;// Return Valueが出力用パラメータに置き換えられる。
}
MyClass ret = f();// この呼出しは以下の呼出しに置き換えられてコピーが発生しない。
MyClass ret;
f_optimized(&ret);// 関数内では、関数の呼出元が用意したメモリ領域(ret)を直接扱う。
2 MOVE SEMANTICS
特定の状況ではオブジェクトのコピーは不要では?例えば、2度使
わないオブジェクト(関数の戻り値等)
X func(){
X x;
// xに対して何かする。
return x;
}
// funcの呼び出し側。
X ret = func();// retにfuncの戻り値をコピーする。コピー必要か?
2.1 MOVE!
コピー不要・不可能なケースではオブジェクトの内部リソース(メモ
リ等)の所有権を移譲(move)すれば良い!
struct X{
BigData* p; // Excelで処理しなければいけない程のビックデータ(リソース)へのポインタ。
};
// コピー(Javaで言うところのclone)のケースではBigDataをコピーする必要がある。
X original = ...;
X copy = original; // originalのBigDataをコピー。時間が掛る。
predictFuture(copy);// コピーへの操作はoriginalへ影響しない。
// retを操作して影響を受けるoriginalはいない!funcの戻り値をretへコピーする必要なし!
X ret = func(); // BigDataをコピーするのではなく、ポインタ(32/64bit)を戻り値からretへコピーするだけで良い。
predictFuture(ret);
2.2 寄り道(リソースの所有権)
C++のようにGCがない言語ではリソースの所有権という概念が非
常に重要。
どのオブジェクトがどのリソースを所有しているか(リソースの後片
付けを誰が行うのか)を厳密に表現する必要。 Java/Scalaではリソ
ースへの参照型Tのメンバ変数。C++では以下の5種類に分類。
C++ 型 所有権とコメント
std::unique_ptr<T> 1つのオブジェクトがリソースを専有
std::shared_ptr<T> 複数のオブジェクトがリソースを共有
raw pointer (T*) 所有はせずただのオプショナルなリソースへ
の参照
reference (T&) 所有はせず必須な参照
value (T) 1つのオブジェクトがリソースを専有。リソース
は動的多態不要
面倒(ダングリング参照&循環参照でのメモリ・リーク)だが、モデル
の意味を厳密に表現 & プログラムの性能を精確に予測可能。
Scala -> C++の移植はツライ。。。主語・目的語が省略された日本
語を英語に翻訳するみたいな感じ。
3 L/R VALUE
l(L)value(左辺値)&r(R)value(右辺値)
lvalueとはメモリ上に実体がありアドレスを取得できるオブジェク
ト。rvalueはアドレス取得不可能なもの。 名前の由来は代入演算
子(=)の左辺になれるものがlvalue。右辺にしかなれないものが
rvalue。
void f(int a){
std::cout << &a << std::endl; // aはlvalue。
}
int g(int a) {
return a;
}
int main() {
int b = 0;
std::cout << &b << std::endl; // bはlvalue。
f(b);
std::cout << &0 << std::endl; // リテラルはrvalue。コンパイル・エラー。
std::cout << &(g(b)) << std::endl; // 関数の戻り値はrvalue。コンパイル・エラー。
return 0;
}
4 L/R VALUE REFERENCE
L/R Valueへの参照。
int main() {
int a = 0; // lvalue 'a'
int& la = a; // lvalue reference to lvalue 'a'
int&& ra = a; // rvalue reference to lvalueは不可能。コンパイル・エラー。
int& lx = 0; // lvalue reference to rvalueは不可能。コンパイル・エラー。
int&& rx = 0; // rvalue reference to rvalue。
const int& cla = a; // const lvalue reference to lvalue。
const int&& cra = a; // const rvalue reference to lvalue。コンパイル・エラー。
const int& clx = 0; // const lvalue reference to rvalue。
const int&& crx = 0; // const rvalue reference to rvalue。
return 0;
}
4.1 注意点(L/R VALUE と L/R VALUE REFERENCE)
L/R Valueは値のカテゴリ(型のことじゃないよ!)、L/R Value
Referenceは型。 L/R Valueと L/R Value Referenceは直交した概
念。
int f(){
return 0;
}
int main() {
int a = 0; // aはint型でlvalueカテゴリ。
int& l = a; // lはintへのlvalue reference型でlvalueカテゴリ。
int&& r = f(); // rはintへのrvalue reference型でlvalueカテゴリ。
int&& rr = r; // コンパイル・エラー。rはlvalueなのでrvalue referenceへは束縛できない。
return 0;
}
5 NON COPYIABLE OBJECT(MOVABLE OBJECT)
本質的にコピーできないオブジェクトがある。例えばthreadオブジ
ェクトを"コピーする"とはどういう意味? コピーした時点から2つのス
レッドが実行される?
void task(){}
int main() {
std::thread t(task);
std::thread copy = t;// スレッドをコピーするとはどういう意味?
return 0;
}
6 MOVE SEMANTICS導入前後
コピーが高価なリソースを保持するオブジェクト & Non Copyiable
Objectの扱いは非常に煩雑。
6.1 MOVE SEMANTICS導入前
不定な一時変数やら、手続型カッコ悪い。
// Move Semantics導入前
struct BigData {
void* peta_byte_data; // ガチで大きなリソース。
};
// DBからビッグ・データを複数読み込む。
void readFromDB(std::vector<BigData>& ret){ }
void predictFuture(std::vector<BigData>& d) {}
int main() {
std::vector<BigData> data; // コピーを防ぐため、一時変数を定義。
readFromDB(data); // 一時変数の領域へDBから読み込んだデータを書き込む。
predictFuture(data);
return 0;
}
6.2 MOVE SEMANTICS導入後
性能を犠牲にせずより関数型的に記述できる。
struct BigData {
void* peta_byte_data; // ガチで大きなリソース。
};
std::vector<BigData> readFromDB(){ }// 手続から関数へ。
void predictFuture(std::vector<BigData> d) {}
int main() {
predictFuture(readFromDB());// 一時変数不要に。
return 0;
}
6.3 MOVE SEMANTICSの注意点
moveの恩恵を受けることができるのは、moveがcopyと比較して
安価なときだけ。
例えばBigDataのようなデータ構造では、copyはPeta Byteコピー
しなければいけないのに対して、 moveはポインタのコピー
(32/64bit)だけなのでmoveはcopyと比較して非常に安価。
一方、標準のarrayはスタック上にメモリが確保されることが義務
付けられているため、 データのコピーは不可避。
struct BigData {
void* peta_byte_data; // ガチで大きなリソース。
};
template<typename base_t, typename expo_t>
constexpr base_t POW(base_t base, expo_t expo) {
return (expo != 0 )? base * POW(base, expo -1) : 1;
}
constexpr auto Peta = POW(10l,15l);// Peta = 10^15
BigData f() {
BigData x; // fのスタック上に配置されるのはポインタ(peta_byte_data)のみ。
return x; // 関数fの終了後ポインタは破棄されるがポインタが指す先の領域は生き残る。
}
std::array<int, Peta> g() {
std::array<int, Peta> y; // Peta Byteのデータがgのスタック上に確保される。
return y; // yのデータは関数gの終了とともに破棄される。
}
int main() {
BigData good = f(); // moveの恩恵を受けることができる。
std::array<int, Peta> bad = g(); // moveの恩恵は受けることができない。(コピーされる)
return 0;
}
6.4 MOVE FOR USER-DEFINED TYPE
gcc/clangでは、ユーザ定義型がmove semanticsをサポートできる
とき、 ユーザ定義型は自動的にMovableな型(Move Constructor
& Move Assignment Operatorが定義された型)にな る。しかし
Visual Studioでは自分でユーザ定義型をMovableにする必要が
あり。VSのおかげでmoveが実装できるようになりました(感謝!)
void acquire(void*) {} // リソースの取得。
void release(void*) {} // リソースの開放。
struct BigData {
void* peta_byte_data; // Resource
// RAII (Resource Accquisition Is Initialization)
BigData(){ acquire(peta_byte_data); }
~BigData(){ release(peta_byte_data); }
// Move Semantics
BigData(BigData&& o){ // Move Constructor
peta_byte_data = o.peta_byte_data; // ポインタのコピー。
o.peta_byte_data = nullptr; // move元のポインタをnullに設定してリソースの2重開放を防止。
}
BigData& operator=(BigData&& o){ // Move Assignment Operator
if(this != &o) { // 自己代入禁止。
release(peta_byte_data); // 自分のリソースを開放。
peta_byte_data = o.peta_byte_data; // ポインタのコピー。
o.peta_byte_data = nullptr; // move元のポインタをnullに設定してリソースの2重開放を防止。
}
return *this;
}
};
6.5 COPY VS. MOVE
オブジェクトはいつcopy(Copy Constructor/Assignemt Operator
が呼ばれる)されて、 いつmove(Move Constructor/Assignemt
Operatorが呼ばれる)されるの?
通常の関数のoverload解決ルールと一緒。bindの可否と優先順
位。結論、rvalueはmoveされる。
  Value    
  R V L V(non const型) L V (const 型)
R V Reference できる(優先) できない できない
L V Reference できない できる(優先) できない
const L V Reference できる できる できる
struct MyClass {
MyClass() {}
// Copy Semantics
MyClass(const MyClass& o){ std::cout << "Copy Constructor" << std::endl; }
MyClass& operator=(const MyClass& o) { std::cout << "Copy Assignment Operator" << std::endl; return *this
// Move Semantics
MyClass(MyClass&& o){ std::cout << "Move Constructor" << std::endl; }
MyClass& operator=(MyClass&& o) { std::cout << "Move Assignment Operator" << std::endl; return *this; }
};
void f( MyClass&& x) { std::cout << "R V Reference" << std::endl; }
void f( MyClass& x) { std::cout << "L V Reference" << std::endl;}
void f(const MyClass& x) { std::cout << "const L V Reference" << std::endl;}
int main() {
f(MyClass()); // R V Reference
MyClass x; // xはnon const型のL V。
f(x); // L V Reference
const MyClass y; // yはconst型のL V。
f(y); // const L V Reference
MyClass a;
MyClass b = a; // Copy Constructor
MyClass c;
c = a; // Copy Assignment Operator
MyClass d = std::move(a); // Move Constructor。std::moveは引数へのをrvalue referenceを取得する。後述。
MyClass e;
e = std::move(b); // Move Assignment Operator
return 0;
}
6.6 COPY VS. MOVE (その2)
void f(MyClass&& x){ // xはL Value? R Value?
MyClass y = x; // Copy or Moved?
}
6.7 MOVE SEMANTICSの実装について
例外安全のために実際にMove Semanticsを実装するときは
noexceptにできるか熟考しましょう。
例外安全についてここに記すには余白が小さすぎるので、また別
の機会に。
7 STD::MOVE & STD::FORWARD
std::moveはlvalueをrvalueに変換する。
std::forwardは条件付でlvalueをrvalueに変換する。
struct MyClass{};
void f(MyClass&& x){
MyClass y = std::move(x); // xはrvalue referenceなのでxにはR Valueが束縛されているためmoveは安全。
}
void f(const MyClass& x) {
MyClass y = std::move(x); // xはlvalue referenceなのでxにはL Valueが束縛されているためmoveは危険。
}
7.1 TYPE DEDUCTION
関数テンプレートは実引数からその引数の型を推論することがで
きる。
template <typename T>
void f(T x) {
std::cout << typeid(x).name() << std::endl;
assert(typeid(x) == typeid(int));
}
int main() {
int a = 0;
f(a); // Tはintと推論される。
const int b = 0;
f(b); // Tはconst intと推論される。
int& l = a;
f(l); // Tはintと推論される。
int&& r = 0;
f(r); // Tはintと推論される。
return 0;
}
7.2 FORWARDING(UNIVERSAL) REFERENCE
template parameterにreferenceやconstが付属すると、推論され
た型Tとオブジェクトxの型は違う。
Forwarding Referenceを使用すると、R Valueが関数テンプレート
に渡されたときTはそのR Valueの型に、 L Valueが渡されたときL
Value Referenceに推論される。
template <typename T> struct TD; // コンパイラの型推論の結果を表示するテクニック。
template <typename T>
void f(T&& x){ // Forwarding(Universal) Reference。
TD<T> a; // メモ: コンパイル・エラーで型が表示される。
// error: implicit instantiation of undefined template 'TD<int &>'
// TD<decltype(x)> a;
}
int main() {
int a = 0;
f(a); // Tはint&と推論される。xの型はint&。(LVR)
const int b = 0;
f(b); // Tはconst int&と推論される。xの型はconst int&。(const LVR)
int& l = a;
f(l); // Tはint&と推論される。xの型はint&。(LVR)
int&& r = 0;
f(r); // Tはint&と推論される。xの型はint&。(LVR)
f(int(0)); // Tはintと推論される。xの型はint&&。(RVR)
return 0;
}
7.3 FORWARDING REFERENCE & STD::FORAWRD
型推論の結果を考慮して右辺値が渡されたときはmoveで、左辺
値が渡されたときはcopyするためにstd::forwardテンプレートが 使
用できる。
template <typename T>
void f(T&& x){
// std::forwardの宣言。
// template< class T >
// T&& forward( typename std::remove_reference<T>::type& t );
// Tがnon reference(例えばint)のときは、std::forrwardはR Value Referenceに。
// TがL value reference(例えばint&)のときは、std::forrwardはL Value Referenceに。
T tmp = std::forward<T>(x);
}
8 キーワード
lvalue & rvalue
lvalue reference & rvalue reference
forwarding reference
move semantics (Movable)
copy semantics (Copyable)
RAII
type deduction
9 参考情報
"C++ Rvalue References Explained"
"Back to the Basics! Essentials of Modern C++
Style@CPPCON2014," Herb Sutter,
"std::move@cppreference.com"
"Effective Modern C++," Scott Meyers
http://thbecker.net/articles/rvalue_references/section_01.html
https://www.youtube.com/watch?v=xnqTKD8uD64
http://en.cppreference.com/w/cpp/utility/move

Move semantics

  • 1.
  • 2.
    TABLE OF CONTENTS 1.C++ for Java/Scala 1.1. Copy Semantics(Value Semantics) 1.2. Copy Semantics(Value Semantics) 1.3. 寄り道(Reference/Pointer Semantics) 1.4. Where is My Copy? 1.5. Copyの現実 2. Move Semantics 2.1. Move! 2.2. 寄り道(リソースの所有権) 3. L/R Value 4. L/R Value Reference 4.1. 注意点(L/R Value と L/R Value Reference) 5. Non Copyiable Object(Movable Object) 6. Move Semantics導入前後 6.1. Move Semantics導入前 6.2. Move Semantics導入後
  • 3.
    1 C++ FORJAVA/SCALA
  • 4.
    1.1 COPY SEMANTICS(VALUESEMANTICS) C++ではオブジェクトは基本的に組み込み型のように振舞う(Value Semantics)。 Javaで言うところのValue Object(int, String, etc)。 int x = 0; int y = x; y = 10; // xの値は 0 か 10のどちらでしょうか?
  • 5.
    1.2 COPY SEMANTICS(VALUESEMANTICS) コピーとは等価の別オブジェクト(コピー先への変更はコピー元に 影響しない)を作ること。 int x = 0; int y = x; // xをyへコピー assert(x == y); // コピー直後はxとyは等価。 y = 10; // yを変更。 assert(x == 0); // y(コピー先)への変更はx(コピー元)へ影響しない。 assert(y == 10);
  • 6.
    1.3 寄り道(REFERENCE/POINTER SEMANTICS) Reference/Pointerセマンティックスも利用するこができます。 intmain() { int x = 0; int& y = x; // yはxへの参照。 y = 10; assert(x == y); assert(x == 10); assert(y == 10); int* z = &x; // zはxへのポインタ。 *z = 20; // ポインタの指す先(x)を変更。 assert(x == y); // yはxへの参照なのでxが変更されればyも変わる。 assert(x == 20); assert(y == 20); assert(*z == 20); return 0; }
  • 7.
    1.4 WHERE ISMY COPY? 何回コピーするでしょう? class MyClass { public: MyClass() = default; MyClass(const MyClass&) { std::cout << "I'm Copied!n"; } }; MyClass f(MyClass p0, const MyClass& p1) { return p0; } int main() { MyClass m; MyClass n = f(m, m); return 0; }
  • 8.
    1.5 COPYの現実 compilerが最適化してくれる場合もあり。 Return ValueOptimization(Named Return Value Optimization) MyClass f() { return MyClass(); } void f_optimized(MyClass* out){ return *out;// Return Valueが出力用パラメータに置き換えられる。 } MyClass ret = f();// この呼出しは以下の呼出しに置き換えられてコピーが発生しない。 MyClass ret; f_optimized(&ret);// 関数内では、関数の呼出元が用意したメモリ領域(ret)を直接扱う。
  • 9.
    2 MOVE SEMANTICS 特定の状況ではオブジェクトのコピーは不要では?例えば、2度使 わないオブジェクト(関数の戻り値等) Xfunc(){ X x; // xに対して何かする。 return x; } // funcの呼び出し側。 X ret = func();// retにfuncの戻り値をコピーする。コピー必要か?
  • 10.
    2.1 MOVE! コピー不要・不可能なケースではオブジェクトの内部リソース(メモ リ等)の所有権を移譲(move)すれば良い! struct X{ BigData*p; // Excelで処理しなければいけない程のビックデータ(リソース)へのポインタ。 }; // コピー(Javaで言うところのclone)のケースではBigDataをコピーする必要がある。 X original = ...; X copy = original; // originalのBigDataをコピー。時間が掛る。 predictFuture(copy);// コピーへの操作はoriginalへ影響しない。 // retを操作して影響を受けるoriginalはいない!funcの戻り値をretへコピーする必要なし! X ret = func(); // BigDataをコピーするのではなく、ポインタ(32/64bit)を戻り値からretへコピーするだけで良い。 predictFuture(ret);
  • 11.
  • 12.
    C++ 型 所有権とコメント std::unique_ptr<T>1つのオブジェクトがリソースを専有 std::shared_ptr<T> 複数のオブジェクトがリソースを共有 raw pointer (T*) 所有はせずただのオプショナルなリソースへ の参照 reference (T&) 所有はせず必須な参照 value (T) 1つのオブジェクトがリソースを専有。リソース は動的多態不要 面倒(ダングリング参照&循環参照でのメモリ・リーク)だが、モデル の意味を厳密に表現 & プログラムの性能を精確に予測可能。 Scala -> C++の移植はツライ。。。主語・目的語が省略された日本 語を英語に翻訳するみたいな感じ。
  • 13.
    3 L/R VALUE l(L)value(左辺値)&r(R)value(右辺値) lvalueとはメモリ上に実体がありアドレスを取得できるオブジェク ト。rvalueはアドレス取得不可能なもの。名前の由来は代入演算 子(=)の左辺になれるものがlvalue。右辺にしかなれないものが rvalue。 void f(int a){ std::cout << &a << std::endl; // aはlvalue。 } int g(int a) { return a; } int main() { int b = 0; std::cout << &b << std::endl; // bはlvalue。 f(b); std::cout << &0 << std::endl; // リテラルはrvalue。コンパイル・エラー。 std::cout << &(g(b)) << std::endl; // 関数の戻り値はrvalue。コンパイル・エラー。 return 0; }
  • 14.
    4 L/R VALUEREFERENCE L/R Valueへの参照。 int main() { int a = 0; // lvalue 'a' int& la = a; // lvalue reference to lvalue 'a' int&& ra = a; // rvalue reference to lvalueは不可能。コンパイル・エラー。 int& lx = 0; // lvalue reference to rvalueは不可能。コンパイル・エラー。 int&& rx = 0; // rvalue reference to rvalue。 const int& cla = a; // const lvalue reference to lvalue。 const int&& cra = a; // const rvalue reference to lvalue。コンパイル・エラー。 const int& clx = 0; // const lvalue reference to rvalue。 const int&& crx = 0; // const rvalue reference to rvalue。 return 0; }
  • 15.
    4.1 注意点(L/R VALUEと L/R VALUE REFERENCE) L/R Valueは値のカテゴリ(型のことじゃないよ!)、L/R Value Referenceは型。 L/R Valueと L/R Value Referenceは直交した概 念。 int f(){ return 0; } int main() { int a = 0; // aはint型でlvalueカテゴリ。 int& l = a; // lはintへのlvalue reference型でlvalueカテゴリ。 int&& r = f(); // rはintへのrvalue reference型でlvalueカテゴリ。 int&& rr = r; // コンパイル・エラー。rはlvalueなのでrvalue referenceへは束縛できない。 return 0; }
  • 16.
    5 NON COPYIABLEOBJECT(MOVABLE OBJECT) 本質的にコピーできないオブジェクトがある。例えばthreadオブジ ェクトを"コピーする"とはどういう意味? コピーした時点から2つのス レッドが実行される? void task(){} int main() { std::thread t(task); std::thread copy = t;// スレッドをコピーするとはどういう意味? return 0; }
  • 17.
  • 18.
    6.1 MOVE SEMANTICS導入前 不定な一時変数やら、手続型カッコ悪い。 //Move Semantics導入前 struct BigData { void* peta_byte_data; // ガチで大きなリソース。 }; // DBからビッグ・データを複数読み込む。 void readFromDB(std::vector<BigData>& ret){ } void predictFuture(std::vector<BigData>& d) {} int main() { std::vector<BigData> data; // コピーを防ぐため、一時変数を定義。 readFromDB(data); // 一時変数の領域へDBから読み込んだデータを書き込む。 predictFuture(data); return 0; }
  • 19.
    6.2 MOVE SEMANTICS導入後 性能を犠牲にせずより関数型的に記述できる。 structBigData { void* peta_byte_data; // ガチで大きなリソース。 }; std::vector<BigData> readFromDB(){ }// 手続から関数へ。 void predictFuture(std::vector<BigData> d) {} int main() { predictFuture(readFromDB());// 一時変数不要に。 return 0; }
  • 20.
    6.3 MOVE SEMANTICSの注意点 moveの恩恵を受けることができるのは、moveがcopyと比較して 安価なときだけ。 例えばBigDataのようなデータ構造では、copyはPetaByteコピー しなければいけないのに対して、 moveはポインタのコピー (32/64bit)だけなのでmoveはcopyと比較して非常に安価。 一方、標準のarrayはスタック上にメモリが確保されることが義務 付けられているため、 データのコピーは不可避。
  • 21.
    struct BigData { void*peta_byte_data; // ガチで大きなリソース。 }; template<typename base_t, typename expo_t> constexpr base_t POW(base_t base, expo_t expo) { return (expo != 0 )? base * POW(base, expo -1) : 1; } constexpr auto Peta = POW(10l,15l);// Peta = 10^15 BigData f() { BigData x; // fのスタック上に配置されるのはポインタ(peta_byte_data)のみ。 return x; // 関数fの終了後ポインタは破棄されるがポインタが指す先の領域は生き残る。 } std::array<int, Peta> g() { std::array<int, Peta> y; // Peta Byteのデータがgのスタック上に確保される。 return y; // yのデータは関数gの終了とともに破棄される。 } int main() { BigData good = f(); // moveの恩恵を受けることができる。 std::array<int, Peta> bad = g(); // moveの恩恵は受けることができない。(コピーされる) return 0; }
  • 22.
    6.4 MOVE FORUSER-DEFINED TYPE gcc/clangでは、ユーザ定義型がmove semanticsをサポートできる とき、 ユーザ定義型は自動的にMovableな型(Move Constructor & Move Assignment Operatorが定義された型)にな る。しかし Visual Studioでは自分でユーザ定義型をMovableにする必要が あり。VSのおかげでmoveが実装できるようになりました(感謝!)
  • 23.
    void acquire(void*) {}// リソースの取得。 void release(void*) {} // リソースの開放。 struct BigData { void* peta_byte_data; // Resource // RAII (Resource Accquisition Is Initialization) BigData(){ acquire(peta_byte_data); } ~BigData(){ release(peta_byte_data); } // Move Semantics BigData(BigData&& o){ // Move Constructor peta_byte_data = o.peta_byte_data; // ポインタのコピー。 o.peta_byte_data = nullptr; // move元のポインタをnullに設定してリソースの2重開放を防止。 } BigData& operator=(BigData&& o){ // Move Assignment Operator if(this != &o) { // 自己代入禁止。 release(peta_byte_data); // 自分のリソースを開放。 peta_byte_data = o.peta_byte_data; // ポインタのコピー。 o.peta_byte_data = nullptr; // move元のポインタをnullに設定してリソースの2重開放を防止。 } return *this; } };
  • 24.
    6.5 COPY VS.MOVE オブジェクトはいつcopy(Copy Constructor/Assignemt Operator が呼ばれる)されて、 いつmove(Move Constructor/Assignemt Operatorが呼ばれる)されるの? 通常の関数のoverload解決ルールと一緒。bindの可否と優先順 位。結論、rvalueはmoveされる。   Value       R V L V(non const型) L V (const 型) R V Reference できる(優先) できない できない L V Reference できない できる(優先) できない const L V Reference できる できる できる
  • 25.
    struct MyClass { MyClass(){} // Copy Semantics MyClass(const MyClass& o){ std::cout << "Copy Constructor" << std::endl; } MyClass& operator=(const MyClass& o) { std::cout << "Copy Assignment Operator" << std::endl; return *this // Move Semantics MyClass(MyClass&& o){ std::cout << "Move Constructor" << std::endl; } MyClass& operator=(MyClass&& o) { std::cout << "Move Assignment Operator" << std::endl; return *this; } }; void f( MyClass&& x) { std::cout << "R V Reference" << std::endl; } void f( MyClass& x) { std::cout << "L V Reference" << std::endl;} void f(const MyClass& x) { std::cout << "const L V Reference" << std::endl;} int main() { f(MyClass()); // R V Reference MyClass x; // xはnon const型のL V。 f(x); // L V Reference const MyClass y; // yはconst型のL V。 f(y); // const L V Reference MyClass a; MyClass b = a; // Copy Constructor MyClass c; c = a; // Copy Assignment Operator MyClass d = std::move(a); // Move Constructor。std::moveは引数へのをrvalue referenceを取得する。後述。 MyClass e; e = std::move(b); // Move Assignment Operator return 0; }
  • 26.
    6.6 COPY VS.MOVE (その2) void f(MyClass&& x){ // xはL Value? R Value? MyClass y = x; // Copy or Moved? }
  • 27.
    6.7 MOVE SEMANTICSの実装について 例外安全のために実際にMoveSemanticsを実装するときは noexceptにできるか熟考しましょう。 例外安全についてここに記すには余白が小さすぎるので、また別 の機会に。
  • 28.
    7 STD::MOVE &STD::FORWARD std::moveはlvalueをrvalueに変換する。 std::forwardは条件付でlvalueをrvalueに変換する。 struct MyClass{}; void f(MyClass&& x){ MyClass y = std::move(x); // xはrvalue referenceなのでxにはR Valueが束縛されているためmoveは安全。 } void f(const MyClass& x) { MyClass y = std::move(x); // xはlvalue referenceなのでxにはL Valueが束縛されているためmoveは危険。 }
  • 29.
    7.1 TYPE DEDUCTION 関数テンプレートは実引数からその引数の型を推論することがで きる。 template<typename T> void f(T x) { std::cout << typeid(x).name() << std::endl; assert(typeid(x) == typeid(int)); } int main() { int a = 0; f(a); // Tはintと推論される。 const int b = 0; f(b); // Tはconst intと推論される。 int& l = a; f(l); // Tはintと推論される。 int&& r = 0; f(r); // Tはintと推論される。 return 0; }
  • 30.
    7.2 FORWARDING(UNIVERSAL) REFERENCE templateparameterにreferenceやconstが付属すると、推論され た型Tとオブジェクトxの型は違う。 Forwarding Referenceを使用すると、R Valueが関数テンプレート に渡されたときTはそのR Valueの型に、 L Valueが渡されたときL Value Referenceに推論される。
  • 31.
    template <typename T>struct TD; // コンパイラの型推論の結果を表示するテクニック。 template <typename T> void f(T&& x){ // Forwarding(Universal) Reference。 TD<T> a; // メモ: コンパイル・エラーで型が表示される。 // error: implicit instantiation of undefined template 'TD<int &>' // TD<decltype(x)> a; } int main() { int a = 0; f(a); // Tはint&と推論される。xの型はint&。(LVR) const int b = 0; f(b); // Tはconst int&と推論される。xの型はconst int&。(const LVR) int& l = a; f(l); // Tはint&と推論される。xの型はint&。(LVR) int&& r = 0; f(r); // Tはint&と推論される。xの型はint&。(LVR) f(int(0)); // Tはintと推論される。xの型はint&&。(RVR) return 0; }
  • 32.
    7.3 FORWARDING REFERENCE& STD::FORAWRD 型推論の結果を考慮して右辺値が渡されたときはmoveで、左辺 値が渡されたときはcopyするためにstd::forwardテンプレートが 使 用できる。 template <typename T> void f(T&& x){ // std::forwardの宣言。 // template< class T > // T&& forward( typename std::remove_reference<T>::type& t ); // Tがnon reference(例えばint)のときは、std::forrwardはR Value Referenceに。 // TがL value reference(例えばint&)のときは、std::forrwardはL Value Referenceに。 T tmp = std::forward<T>(x); }
  • 33.
    8 キーワード lvalue &rvalue lvalue reference & rvalue reference forwarding reference move semantics (Movable) copy semantics (Copyable) RAII type deduction
  • 34.
    9 参考情報 "C++ RvalueReferences Explained" "Back to the Basics! Essentials of Modern C++ Style@CPPCON2014," Herb Sutter, "std::[email protected]" "Effective Modern C++," Scott Meyers http://thbecker.net/articles/rvalue_references/section_01.html https://www.youtube.com/watch?v=xnqTKD8uD64 http://en.cppreference.com/w/cpp/utility/move