で、結局のところ右辺値参照ってなんなのさ?
いよいよ、右辺値参照とは何か、ということです。
ムーブコンストラクタと、コピーコンストラクタの話を前にしました。
ここで、
ムーブコンストラクタを持つオブジェクトは「移動可能オブジェクト(movable object)」である。
コピーコンストラクタを持つオブジェクトは「コピー可能オブジェクト(copyable object)」であると考えましょう。
で、
右辺値参照を受け取るときはどういうときか、というと、
「このスコープを抜けると破棄される一時オブジェクトを束縛した!!
今すぐ可能ならば移動を行ってくれ!!(行わなければならない!!)」
というときです。
「右辺値」となるオブジェクトは「すぐに破棄されてしまうことが解っているオブジェクト」です。
要するにこれが渡ってきます。
「破棄されてしまうオブジェクトを移動するのかコピーするのかどっちがお得?」
というのが「右辺値参照(rvalue reference)」の持つ効率化の意味です。
すなわち、この恩恵を受けられるのは「移動可能オブジェクト(movable object)」だけです。
例えば、
struct BigFatObject { int foolData_[10000]; };
こんなクラスは恩恵を受けられるでしょうか?
答えは「受けられません」です。
POD (Plain Old Data)にとって「移動」は「コピー」と等価であり、
PODを高速に「移動」する事はできないからです。
故にPODのときにはコピーをせざるを得ません。
(PODとは何だ、という人は単純な構造体とでも思ってください。これはまた別の機会にでも)
なので、このオブジェクトに対して右辺値参照をとるような関数が呼ばれても、
このオブジェクトは可及的速やかにコピーをするくらいしかできません。
BigFatObject(BigFatObject&& rhs) {
{ // ムーブコンストラクタで右辺値参照はこのように示される、しかしこれを「高速に移動」する手段はコピーしかない
...
}
となります。
では、どういったオブジェクトが恩恵を受けるでしょう?
それは、
struct BigFatMovableObject { BigFatMovableObject() : foolData_(new int[10000]) { } ... private: int* foolData_; };
のようなオブジェクトです。
上記のオブジェクトと比べて何が違うか、といえば
「このオブジェクトが持つ所有権はポインタの移動によって移動可能である」
ということです。
これは、「右辺値参照(rvalue reference)」と「ムーブセマンティクス」の格好の餌食です。
では例を示します。
gcc44で0x拡張をONにして実行します。(g++-mp-4.4を使用しています。MacPorts版)
テストするオブジェクトはこれ。無駄にでかいデータを持っていますが、移動可能であり所有権の監視をします。
念のため、他の操作が呼ばれないかをprintして確認します。
struct BigFatMovableObject { BigFatMovableObject() : foolData_(new int[10000]) { cout << "BigFatMovableObject::BigFatMovableObject()" << endl; cout << "Address:" << foolData_ << endl; } ~BigFatMovableObject() { cout << "BigFatMovableObject::~BigFatMovableObject()" << endl; cout << "Address:" << foolData_ << endl; cout << "Has Ownership ?:" << ((foolData_) ? "YES" : "NO") << endl; delete [] foolData_; foolData_ = 0; } BigFatMovableObject(const BigFatMovableObject& rhs) { cout << "BigFatMovableObject(const BigFatMovableObject& rhs)" << endl; cout << "Why ?" << endl; } BigFatMovableObject(BigFatMovableObject&& rhs) : foolData_(rhs.foolData_) { cout << "BigFatMovableObject(BigFatMovableObject&& rhs)" << endl; cout << "Address:" << rhs.foolData_ << endl; rhs.foolData_ = 0; } BigFatMovableObject&& operator=(BigFatMovableObject&& rhs) { cout << "BigFatMovableObject& operator=(const BigFatMovableObject& rhs)" << endl; return *this; } BigFatMovableObject& operator=(const BigFatMovableObject& rhs) { cout << "BigFatMovableObject& operator=(const BigFatMovableObject& rhs)" << endl; return *this; } private: int* foolData_; };
で、我々が最も素直に恩恵を受けるvectorで試します。
(関数などの場合RVO[==戻り値最適化]の効果もあってわかりにくい)
#include <vector> int main() { std::vector<BigFatMovableObject> v; cout << "----- 1st push_back -----" << endl; v.push_back(BigFatMovableObject()); cout << "----- 2nd push_back -----" << endl; v.push_back(BigFatMovableObject()); cout << "----- The End. -----" << endl; }
では、こんな感じで。
名前をつけないオブジェクトを渡すことで右辺値参照が使われることを期待します。
解説はログのあと!
----- 1st push_back ----- BigFatMovableObject::BigFatMovableObject() Address:0x100801000 BigFatMovableObject(BigFatMovableObject&& rhs) Address:0x100801000 BigFatMovableObject::~BigFatMovableObject() Address:0 Has Ownership ?:NO ----- 2nd push_back ----- BigFatMovableObject::BigFatMovableObject() Address:0x10080ae00 BigFatMovableObject(BigFatMovableObject&& rhs) Address:0x10080ae00 BigFatMovableObject(BigFatMovableObject&& rhs) Address:0x100801000 BigFatMovableObject::~BigFatMovableObject() Address:0 Has Ownership ?:NO BigFatMovableObject::~BigFatMovableObject() Address:0 Has Ownership ?:NO ----- The End. ----- BigFatMovableObject::~BigFatMovableObject() Address:0x100801000 Has Ownership ?:YES BigFatMovableObject::~BigFatMovableObject() Address:0x10080ae00 Has Ownership ?:YES
結果。
解説していきます。
----- 1st push_back ----- BigFatMovableObject::BigFatMovableObject() Address:0x100801000
push_backに渡した一時オブジェクトがコンストラクションされました。
BigFatMovableObject(BigFatMovableObject&& rhs) Address:0x100801000
一時オブジェクトなので「右辺値参照」によって、移動を行わなければなりません。
破棄されることが解っているオブジェクトなので、ポインタを盗用します。
単にポインタを移動して、右辺値参照されたオブジェクトの破棄に備えます。
移動のために右辺値のデータを触ることが許されます。
(このために右辺値参照は殆どの場合constでは渡されません。ここがコピーコンストラクタとは違います)
BigFatMovableObject(BigFatMovableObject&& rhs)
: foolData_(rhs.foolData_)
{
rhs.foolData_ = 0;
}
BigFatMovableObject::~BigFatMovableObject() Address:0 Has Ownership ?:NO
移動し終わった一時オブジェクトは所有権を失ったまま破棄されます。
----- 2nd push_back ----- BigFatMovableObject::BigFatMovableObject() Address:0x10080ae00
二回目のpush_backに渡した一時オブジェクトがコンストラクションされました。
BigFatMovableObject(BigFatMovableObject&& rhs) Address:0x10080ae00 BigFatMovableObject(BigFatMovableObject&& rhs) Address:0x100801000
1st で移動した筈のムーブコンストラクタが再度呼び出されますが、
これはvectorの「リアロケーション」によるものです。
用心深い人はcout << v.capacity() << endl;を挟もう!
これで、この二つのオブジェクトを移動します。
(これがムーブセマンティクス!!)<重要
BigFatMovableObject::~BigFatMovableObject() Address:0 Has Ownership ?:NO BigFatMovableObject::~BigFatMovableObject() Address:0 Has Ownership ?:NO
移動元になった一時オブジェクトは所有権を失って破棄されます。
----- The End. ----- BigFatMovableObject::~BigFatMovableObject() Address:0x100801000 Has Ownership ?:YES BigFatMovableObject::~BigFatMovableObject() Address:0x10080ae00 Has Ownership ?:YES
最後にvectorの消滅とともに所有権を持ったままコンテナの中のオブジェクトが破棄されます。
これが、従来の
「コピーセマンティクス」と「コピーコンストラクタ」
からより効率的になった
「ムーブセマンティクス」と「ムーブコンストラクタ」
の力です。
お疲れ様でした!!