第6章:セマンティクスの移動とenable_if <>
第6章モバイルセマンティクスとenable_if <>
C ++ 11で導入された最も顕著な機能の1つは、移動セマンティクスでした。これを使用すると、コンテンツをコピーする代わりに、ソースオブジェクトから宛先オブジェクトに内部リソースを移動(「盗む」)することで、コピーと割り当てを最適化できます。これは、ソースが内部の値または状態を必要としなくなった場合に実行できます(破棄されようとしているため)。
モバイルセマンティクスは、C ++ 11で導入された最も顕著な機能の1つです。移動セマンティクスは、コピーおよび割り当て操作を最適化できます。ソースオブジェクトの内容をコピーする代わりに、ソースオブジェクトの内部リソースをターゲットオブジェクトに移動(「スチール」)します。これは、ソースオブジェクトが内部の値や状態を必要としないことを前提としています(ソースオブジェクトが破棄されるため)。
移動セマンティクスはテンプレートの設計に大きな影響を与えます。また、汎用コードで移動セマンティクスをサポートするために特別なルールが導入されました。この章では、これらの機能を紹介します。
モバイルのセマンティクスはテンプレートの設計に重要な影響を与えます。モバイルセマンティクスをサポートするために、いくつかの特別なルールが汎用コードに導入されました。この章では、これらの機能を紹介します。
6.1完全転送
6.1 完全な転送
渡された引数の基本的なプロパティを転送する汎用コードを記述したいとします。
着信パラメーターの基本属性を転送する汎用コードを作成する場合:
•変更可能なオブジェクトは、引き続き変更できるように転送する必要があります。
変更可能なオブジェクトは、転送後も変更可能です。
•定数オブジェクトは、読み取り専用オブジェクトとして転送する必要があります。
constオブジェクトは、読み取り専用オブジェクトとしてのみ転送できます。
•移動可能なオブジェクト(有効期限が近づいているために「盗む」ことができるオブジェクト)は、移動可能なオブジェクトとして転送する必要があります。
リムーバブルオブジェクト(期限切れが近づいているためにリソースが盗まれる可能性のあるオブジェクト)は、転送後も引き続きリムーバブルです。
テンプレートなしでこの機能を実現するには、3つのケースすべてをプログラムする必要があります。
テンプレートを使用せずにこの目標を達成するには、上記の3つの状況を個別にプログラムする必要があります。
たとえば、f()の呼び出しを対応する関数g()に転送するには:
たとえば、f()を呼び出すときに渡されたパラメーターを対応するg()関数に転送するには、次のようにします。
#include <utility> #include <iostream> class X { ... }; void g(X&){ std :: cout << " g()for variable \ n " ; } void g(X const&){ std :: cout << " g()for constant \ n " ; } void g(X && ){ std :: cout << " 可動オブジェクトのg()\ n " ; } // f()に引数valをg()に転送させる:void f(X& g(val); // valは非const lvalue =>呼び出しg(X&) } void f(X const&val){ g(val); // valはconst lvalue =>呼び出しg(X const&) } void f(X && val){ g(std :: move(val)); // valは非定数ですlvalue => g(X &&)を呼び出すにはstd :: move()が必要です } int main() { X v; // 変数 X const c;を作成します // 定数 f(v);を作成します // 非定数オブジェクト呼び出しのf()f(X&)=>呼び出しg(X&) f(c);定数オブジェクト呼び出しのf()f(X const&)=>呼び出しg(X const&) f(X()); // 一時呼び出しのf()f(X &&)=>呼び出しg(X &&) f(std :: move(v)); // 可動変数呼び出しのf()f(X &&)=>呼び出しg(X &&) }
ここでは、引数をg()に転送するf()の3つの異なる実装を示します。
ここでは3つの異なるf()関数が定義されており、それらはパラメーターをg()関数に転送します。
void f(X&val){ g(val); // valは非const lvalue =>呼び出しg(X&) } void f(X const&val){ g(val); // valはconst Lvalue =>コールg(X const&) } void f(X && val){ g(std :: move(val)); // valは非const lvalue => stdを使用する必要がある:: move to call g(X &&) }
(右辺値参照を介した)移動可能なオブジェクトのコードは他のコードとは異なることに注意してください。言語の規則に従って、移動のセマンティクスは渡されないため、std :: move()が必要です。3番目のf()のvalは右辺値参照として宣言されていますが、式として使用した場合、その値カテゴリは非定数lvalue(付録Bを参照)であり、最初のf()のvalとして動作します。move()がなければ、g(&&)の代わりに非定数lvalueのg(X&)が呼び出されます。
移動可能なオブジェクト(右辺値で参照される)のコードは他のコードとは異なることに注意してください。std:: move()を呼び出す必要があります。これは、言語規則に従って、移動のセマンティクスが渡されないためです。3番目のf()関数では、valは右辺値参照として宣言されていますが、式として使用すると、その値の型は非const左辺値になります(付録Bを参照)。 f()関数も同じです。したがって、move()を使用しない場合、g(&&)関数の代わりにg(X&)の非const lvalueバージョンが呼び出されます。
一般的なコードで3つのケースすべてを組み合わせる場合、問題が発生します。
上記の3つの状況をジェネリックコードで統合する場合、問題が発生します。
template <typename T> void f(T val){ g(val); }
最初の2つのケースでは機能しますが、可動オブジェクトが渡される(3番目の)ケースでは機能しません。
このテンプレートは最初の2つの場合にのみ有効ですが、3番目の可動オブジェクトには無効です。
このため、C ++ 11では、完全な転送パラメータに関する特別なルールが導入されています。これを実現する慣用的なコードパターンは次のとおりです。
したがって、C ++ 11では、パラメーターを完全に転送するための特別なルールが導入されています。この目標を達成するための慣用的なコードパターンは次のとおりです。
template <typename T> void f(T && val){ g(std :: forward <T>(val)); // valをg()に完全に転送する }
std :: move()にはテンプレートパラメータがなく、渡された引数に対して「トリガー」移動セマンティクスが渡されるのに対し、std :: forward <>()は渡されたテンプレート引数に応じて潜在的な移動セマンティクスを「転送」することに注意してください。
注:std :: move()にはテンプレートパラメーターがありません。無条件にパラメーターを移動します。そして、std :: forward <>は、渡されたパラメーターの特定の条件に従って、潜在的なモバイルセマンティクスを「転送」するかどうかを決定します。
テンプレートパラメータのT &&が特定のタイプXのX &&として動作するとは限りません。異なるルールが適用されます!ただし、構文的には同じに見えます。
テンプレートパラメータTのT &&が特定のタイプXのX &&と同じであるとは限りません。構文は同じに見えますが、異なるルールに適用されます。
•特定の型のX && Xは、パラメーターを右辺値参照として宣言します。これは、移動可能なオブジェクト(一時オブジェクトなどのprvalue、およびstd :: move()で渡されたオブジェクトなどのxvalue)にのみバインドできます。詳細については、付録Bを参照してください。これは常に変更可能であり、いつでもその値を「盗む」ことができます。
特定のタイプXのX &&は、右辺値参照を宣言します。移動オブジェクトにのみバインドできます(一時オブジェクトなどのprvalue。std:: move(を介して渡されるオブジェクトなどのXvalue)。詳細については、付録Bを参照してください)その値は可変ですが、常に「盗まれる」可能性があります。
•テンプレートパラメータのT &&は、転送参照(ユニバーサル参照とも呼ばれます)を宣言します。可変、不変(つまりconst)、または可動オブジェクトにバインドできます。関数定義内では、パラメーターは変更可能、不変、または内部を「盗む」ことができる値を参照する場合があります。
テンプレートパラメータTのT &&は、前方参照(ユニバーサル参照とも呼ばれます)を宣言します。可変、不変(constなど)、または移動可能なオブジェクトにバインドできます。関数内の定義では、このパラメーターも可変、不変、または内部データを「盗む」ことができる値を指します。
Tは実際にはテンプレートパラメータの名前でなければならないことに注意してください。テンプレートに依存するだけでは不十分です。テンプレートパラメータTの場合、typename T :: iterator &&などの宣言は、右辺値参照であり、転送参照ではありません。
Tはテンプレートパラメータの名前でなければならないことに注意してください。テンプレートパラメータに依存するだけでは十分ではありません。たとえば、テンプレートパラメータTの場合、typname T ::イテレータ&&は、前方参照ではなく、右辺値参照のみを宣言します。
したがって、前向き引数を完成させるプログラム全体は次のようになります。
したがって、パラメータを完全に転送するためのプログラム全体は次のようになります。
include <utility> #include <iostream> class X { ... }; void g(X&){ std :: cout << " g()for variable \ n " ; } void g(X const&){ std :: cout << " g()for constant \ n " ; } void g(X && ){ std :: cout << " 可動オブジェクトのg()\ n " ; } // f()に引数valをg()に完全転送: テンプレート< f(T && val){ g(std :: forward <T>(val)); // すべての有効なパラメータに対して、正のg()関数を使用できます。 } int main() { X v; // 変数 X const c;を作成します // 定数 f(v);を作成します // 変数呼び出しのf()f(X&)=>呼び出しg(X&) f(c); // 定数呼び出しのf()f(X const&)=>呼び出しg(X const&) f(X()); // 一時呼び出しのf()f(X &&)=>呼び出しg(X &&) f(std :: move(v)); // 移動が有効な変数呼び出しのf()f(X &&)=>呼び出しg(X &&) }
もちろん、完全転送を可変個のテンプレートで使用することもできます(いくつかの例については、60ページのセクション4.3を参照してください)。完全転送の詳細については、280ページのセクション15.6.3を参照してください。
もちろん、完全転送は変数テンプレートにも適用できます(他の例については、60ページのセクション4.3を参照してください)。完全転送の詳細については、280ページのセクション15.6.3を参照してください。