関数の一時変数の戻り値は右辺値なのか?
大きなコンテナを一時変数として持っておいて戻り値で返すと巨大なコピーが発生します。
std::vector< int > funct() { std::vector< int > data; //・・・・(中略)・・・ return data; }
なので、参照渡しで中でデータを作るということをよくやってました。
void funct( std::vector< int >& data ){ //・・・・(省略)・・・ }
ただし、C++11時代になって右辺値参照が導入されてこのような心配がなくなったみたいです。実際にいろいろ試してみましたが前者の実装でもコンテナのコピーは発生していないっぽいです。その正体に迫ってみました(大げさ)
毎度おなじみの実験用クラスを用います。
class Test{ public: Test(){ std::cout << "default constractor" << std::endl; } Test( const Test& test ){ std::cout << "copy constractor" << std::endl; } Test& operator=( const Test& test ){ std::cout << "copy operator" << std::endl; } ~Test(){ std::cout << "destractor" << std::endl; } };
こいつを返す関数を作ります。
Test funct(){ Test test; //最適化防止の為に適当に埋めた方がいいかも・・・ return test; }
そして受け取ります。
{ Test test( funct() ); }
これを試しに実行してみると以下の結果になりました。なお環境はVC2010です。
default constractor copy constractor destractor destractor
予想通り、コピーが発生しています。
次に、Testクラスに右辺値参照を受け取るコンストラクタを追加して実験してみます。
class Test{ public: //・・・各種コンストラクタ Test( Test&& test ){ std::cout << "move constractor" << std::endl; } //↑右辺値参照のコンストラクタの追加 };
実行結果は以下のとおり。
default constractor move constractor destractor destractor
コピーは発生せず、代わりに右辺値参照が呼ばれています。どうやら関数の戻り値は、中身の一時変数も含めて右辺値という扱いらしいです。なので右辺値参照のコンストラクタを用意しておけばコスト削減に繋がりそうです。
実際調べてみたら各種コンテナも右辺値参照のコンストラクタを用意しているので、今後は戻り値として使っても問題なさそうです。実装スタイルにちょっとだけ変化が出ました(^^;
また、今後もしコンテナのようにでかいデータでコピー可能なクラスを作る場合は右辺値参照のコンストラクタを実装しておくのが礼儀になるかもしれません。
あ、ちなみにmove constractorというのは私が勝手につけた名前です。一般的にどう呼ばれているかは不明ですorz