[C++]右参照:移動の意味と完全な転送
23384 ワード
C++11が導入した新しい特性のうち,メモリモデルと関連施設を併発するほか,最も魅力的で接地ガスの特性は『右値参照』(rvalue reference)である.右の参照を追加する動機は、不要なリソースコピーを減らすことです.次の手順を考慮します.
vectorに要素を追加するには、string::string(const char*)、string::string(const string&)、string:~string()の3つの関数を前後して呼び出す必要があります.2回のメモリコピーに関連します.1回目は字面定数「string」を使用して一時オブジェクトを構築し、2回目はこの一時オブジェクトを使用してvectorの新しい要素を構築します.「最後に一時的な対象が析出する」.
意味の移動
上記のプログラム操作の問題点は,一時オブジェクトの構造と解析が不要なリソースコピーをもたらすことである.一時オブジェクトを文法的に認識する仕組みがあれば,一時オブジェクトを用いて新しいオブジェクト(コピー構造)を構築する際に,一時オブジェクトが持つリソースを新しいオブジェクトに『移行』することで,このような不要なコピーを排除できる.このような文法的メカニズムが『右引用』であるのに対し、伝統的な引用は『左引用』と呼ばれている.左の値の参照には「&」識別子(string&)、右の値の参照には「&&」識別子(string&&&)が使用されます.ちなみに左(lvalue)とは何か(rvalue):アドレスを取得できる名前付きオブジェクトは左です.値を取得できないオブジェクトは、匿名の一時オブジェクトとすべてのフォント値(literal value)を含む右値です.右値の構文サポートにより、移動の意味を実現するためには、従来のコピーコンストラクタと付与オペレータを右値をパラメータとして再ロードする必要があります.結局、どのリソースが移動できるのか、クラスのみをコピーできるインプリメンテーション者が知っているのか.移動意味のコピー『構造』については、ソースオブジェクトのリソースを目的オブジェクトにバインドし、ソースオブジェクトのリソースへのバインドを解除するのが一般的である.割り当て操作の場合、一般的なプロセスは、まず目的のオブジェクトが持つリソースを破棄し、次にリソースのバインドを変更することです.また、もちろん、従来の構造や付与値と同様に、構造の異常な安全や自己付与も考慮される.プレゼンテーションとして:
右にバインドされた右の値の参照は、名前が付いているため、「左の値」であることに注意してください.考慮:
上のプログラムでは、B::B(B&&&)は呼び出されません.このため、C++11にはstd::move(T&&t)テンプレート関数が導入され、tは右値に変換されます.
std::moveの可能な実装:
バインド規則
右値参照を導入すると、「参照」から「値」へのバインドルールも拡張されます.左値参照は左値:int xにバインドできます.int &xr = x; 非常量の左値参照は右値にバインドできません:int&r=0; 定数左値参照は、左値と右値:int xにバインドできます.const int &cxr = x; const int &cr = 0; 右値参照は右値:int&&r=0にバインドできます. 右値参照は左値:int xにバインドできません.int &&xr = x; 定数右値参照には現実的な意味はありません(結局、右値参照の初志は意味を移動することであり、移動は『修正』を意味します).
ここで、第5の規則『適用しない』関数テンプレートのパラメータは、例えば、以下の関数は任意のタイプのパラメータを受け入れることができ、右値でも左値でも定数でも非常量でもよい.
T&&パラメータは左の値を受け入れることができ、C++11がこのような特殊な状況に対して行ったルールの改訂であり、「完璧な転送」(perfect forwarding)を実現することを目的としている.
完全転送
C++11以前は,パラメータ『転送』の問題があり,完璧な転送が容易に実現できない.転送の目的は、cvプロパティ(const/volatile)や左右値プロパティなど、「参照パラメータ」の追加プロパティを渡すことです.この問題を描くために,左右値属性の伝達を例に(cv属性にも類似の問題がある),以下のクラス定義を参照する.
移動の意味をサポートするには、コンストラクション関数を再ロードする必要があります.コンストラクション関数には2つのパラメータがあるため、右参照と左参照の組み合わせも考慮する必要があります.
コンストラクション関数にn個のパラメータがある場合は、2^n個のリロードが必要です!C++11では,右値参照に基づく関数テンプレートによりこの問題を解決し,本質的には実パラメトリックタイプの推論により,実際の状況に応じてコンパイラによって自動的な『リロード』を完了する.
この転送を紹介する前に,右値がパラメータを参照する関数テンプレートの実パラメータ推定規則,すなわち参照折り畳み(reference collapsing)を知る必要がある.BTW.C++11までは、参照の参照タイプ(reference to reference)にバインドすることはできません.Tをテンプレートのタイプパラメータ、Aを実パラメータの基本タイプとすると、次のようになります.
T
パラメータ
折りたたみT
折りたたみ後の実パラメータタイプ
A&
T&
A
A&
A&
T&&
A&
A&
A&&
T&
A&
A&
A&&
T&&
A
A&&
関数のパラメータがT&&&と宣言されている場合、実パラメータが右または右の値で参照されている場合にのみ、折りたたまれた実パラメータタイプが右の値で参照され、そうでない場合は左の値で参照されます.この折りたたみルールにより,左右値参照属性の転送が可能となる.std::forwardは簡単に実現できます.
まとめ
C++11には多くの特性が導入されており、目の前が明るくなることが多い.多くの特性は一度閲覧すると明らかになるが,右値参照に関連し,特に完璧な転送は相対的に回り道であり,整理しにくい.右の値の参照には2つのアプリケーションがあり、最も基本的な動機は意味を移動することであり、同時に完璧な転送のサポートにきっかけをもたらす.
std::vector v;
v.push_back("string");
vectorに要素を追加するには、string::string(const char*)、string::string(const string&)、string:~string()の3つの関数を前後して呼び出す必要があります.2回のメモリコピーに関連します.1回目は字面定数「string」を使用して一時オブジェクトを構築し、2回目はこの一時オブジェクトを使用してvectorの新しい要素を構築します.「最後に一時的な対象が析出する」.
意味の移動
上記のプログラム操作の問題点は,一時オブジェクトの構造と解析が不要なリソースコピーをもたらすことである.一時オブジェクトを文法的に認識する仕組みがあれば,一時オブジェクトを用いて新しいオブジェクト(コピー構造)を構築する際に,一時オブジェクトが持つリソースを新しいオブジェクトに『移行』することで,このような不要なコピーを排除できる.このような文法的メカニズムが『右引用』であるのに対し、伝統的な引用は『左引用』と呼ばれている.左の値の参照には「&」識別子(string&)、右の値の参照には「&&」識別子(string&&&)が使用されます.ちなみに左(lvalue)とは何か(rvalue):アドレスを取得できる名前付きオブジェクトは左です.値を取得できないオブジェクトは、匿名の一時オブジェクトとすべてのフォント値(literal value)を含む右値です.右値の構文サポートにより、移動の意味を実現するためには、従来のコピーコンストラクタと付与オペレータを右値をパラメータとして再ロードする必要があります.結局、どのリソースが移動できるのか、クラスのみをコピーできるインプリメンテーション者が知っているのか.移動意味のコピー『構造』については、ソースオブジェクトのリソースを目的オブジェクトにバインドし、ソースオブジェクトのリソースへのバインドを解除するのが一般的である.割り当て操作の場合、一般的なプロセスは、まず目的のオブジェクトが持つリソースを破棄し、次にリソースのバインドを変更することです.また、もちろん、従来の構造や付与値と同様に、構造の異常な安全や自己付与も考慮される.プレゼンテーションとして:
class String {
public:
String(const String &rhs) { ... }
String(String &&rhs) {
s_ = rhs.s_;
rhs.s_ = NULL;
}
String& operator=(const String &rhs) { ... }
String& operator=(String &&rhs) {
if (this != &rhs) {
delete [] s_;
s_ = rhs.s_;
rhs.s_ = NULL;
}
return *this;
}
private:
char *s_;
};
右にバインドされた右の値の参照は、名前が付いているため、「左の値」であることに注意してください.考慮:
class B {
public:
B(const B&) {}
B(B&&) {}
};
class D : public B {
D(const D &rhs) : B(rhs) {}
D(D &&rhs) : B(rhs) {}
};
D getD();
D d(getD());
上のプログラムでは、B::B(B&&&)は呼び出されません.このため、C++11にはstd::move(T&&t)テンプレート関数が導入され、tは右値に変換されます.
class D : public B {
D(D &&rhs) : B(std::move(rhs)) {}
};
std::moveの可能な実装:
template <typename T>
typename remove_reference<T>::type&&
move(T &&t) {
return static_cast<remove_reference<T>::type&&>(t);
}
バインド規則
右値参照を導入すると、「参照」から「値」へのバインドルールも拡張されます.
ここで、第5の規則『適用しない』関数テンプレートのパラメータは、例えば、以下の関数は任意のタイプのパラメータを受け入れることができ、右値でも左値でも定数でも非常量でもよい.
template <typename T>
void foo(T &&t);
int x;
const int xx;
foo(x); //~ OK
foo(xx); //~ OK
foo(10); //~ OK
T&&パラメータは左の値を受け入れることができ、C++11がこのような特殊な状況に対して行ったルールの改訂であり、「完璧な転送」(perfect forwarding)を実現することを目的としている.
完全転送
C++11以前は,パラメータ『転送』の問題があり,完璧な転送が容易に実現できない.転送の目的は、cvプロパティ(const/volatile)や左右値プロパティなど、「参照パラメータ」の追加プロパティを渡すことです.この問題を描くために,左右値属性の伝達を例に(cv属性にも類似の問題がある),以下のクラス定義を参照する.
class X
{
public:
X(const std::string &s, const std::vector &v) : s_(s), v_(v) {}
private:
std::string s_;
std::vector v_;
};
移動の意味をサポートするには、コンストラクション関数を再ロードする必要があります.コンストラクション関数には2つのパラメータがあるため、右参照と左参照の組み合わせも考慮する必要があります.
class X
{
public:
X(const std::string &s, const std::vector &v) : s_(s), v_(v) {}
X(std::string &&s, const std::vector &v) : s_(std::move(s)), v_(v) {}
X(const std::string &s, std::vector &&v) : s_(s), v_(std::move(v)) {}
X(std::string &&s, std::vector &&v) : s_(std::move(s)), v_(std::move(v)) {}
private:
std::string s_;
std::vector v_;
};
コンストラクション関数にn個のパラメータがある場合は、2^n個のリロードが必要です!C++11では,右値参照に基づく関数テンプレートによりこの問題を解決し,本質的には実パラメトリックタイプの推論により,実際の状況に応じてコンパイラによって自動的な『リロード』を完了する.
class X
{
public:
template <typename T1, typename T2>
X(T1 &&s, T2 &&v) : s_(std::forward<T1>(s)), v_(std::forward<T2>(v)) {}
private:
std::string s_;
std::vector v_;
};
この転送を紹介する前に,右値がパラメータを参照する関数テンプレートの実パラメータ推定規則,すなわち参照折り畳み(reference collapsing)を知る必要がある.BTW.C++11までは、参照の参照タイプ(reference to reference)にバインドすることはできません.Tをテンプレートのタイプパラメータ、Aを実パラメータの基本タイプとすると、次のようになります.
T
パラメータ
折りたたみT
折りたたみ後の実パラメータタイプ
A&
T&
A
A&
A&
T&&
A&
A&
A&&
T&
A&
A&
A&&
T&&
A
A&&
関数のパラメータがT&&&と宣言されている場合、実パラメータが右または右の値で参照されている場合にのみ、折りたたまれた実パラメータタイプが右の値で参照され、そうでない場合は左の値で参照されます.この折りたたみルールにより,左右値参照属性の転送が可能となる.std::forwardは簡単に実現できます.
template <typename T>
T&& forward(T &&t)
{
return static_cast<T&&>(t);
}
まとめ
C++11には多くの特性が導入されており、目の前が明るくなることが多い.多くの特性は一度閲覧すると明らかになるが,右値参照に関連し,特に完璧な転送は相対的に回り道であり,整理しにくい.右の値の参照には2つのアプリケーションがあり、最も基本的な動機は意味を移動することであり、同時に完璧な転送のサポートにきっかけをもたらす.