暗黙のmoveとNRVO
VS2010(VC10)にて関数で返す値についての扱いをついったーで突っ込まれて実際書いて確かめた時のメモ。
#include <iostream> #include <vector> #include <boost/timer.hpp> struct Test{ std::vector<int> tmp; Test(){ std::cout << "コンストラクタ" << std::endl; } ~Test(){ std::cout << "デストラクタ" << std::endl; } Test(const Test& obj) : tmp( obj.tmp ){ std::cout << "コピーコンストラクタ" << std::endl; } Test(Test&& obj){ std::swap(tmp,obj.tmp); std::cout << "ムーブコンストラクタ" << std::endl; } }; Test get(int n, boost::timer& timer){ Test tmp; for(int i=0; i < n; ++i){ tmp.tmp.push_back(i*2+3*i); } std::cout << timer.elapsed() << std::endl; timer.restart(); return tmp; } int main(){ boost::timer t; auto tmp = get(10000000,t); std::cout << t.elapsed() << std::endl; }
実行結果:
コンストラクタ 0.072 0 デストラクタ
NRVOが聞いてコピーもムーブも起こらずにそのまま置き換えられた。
これが最速だが、ifでreturn分けたりすると最適化が切れる場合が多い。
そうするとどうなるか。てっきり普通にコピーされるのかなーとか思っていた、VC2008脳だった。が。
#include <iostream> #include <vector> #include <boost/timer.hpp> struct Test{ std::vector<int> tmp; Test(){ std::cout << "コンストラクタ" << std::endl; } ~Test(){ std::cout << "デストラクタ" << std::endl; } Test(const Test& obj) : tmp( obj.tmp ){ std::cout << "コピーコンストラクタ" << std::endl; } Test(Test&& obj){ std::swap(tmp,obj.tmp); std::cout << "ムーブコンストラクタ" << std::endl; } }; Test get(int n, boost::timer& timer){ Test tmp; for(int i=0; i < n; ++i){ tmp.tmp.push_back(i*2+3*i); } std::cout << timer.elapsed() << std::endl; timer.restart(); if( n%2 == 0 ){ return tmp; } tmp.tmp.push_back(1192); return tmp; } int main(){ boost::timer t; auto tmp = get(10000000,t); std::cout << t.elapsed() << std::endl; }
実行結果:
コンストラクタ 0.072 ムーブコンストラクタ デストラクタ 0.001 デストラクタ
あ……ハイ。そうですよね、C++0x(11?)では関数内で宣言された変数が返り値になった場合には原則moveされるんでしたねそういえばはい。
という事で暗黙moveされんじゃねーかstd::moveとか書く必要あるのすげー特定場面じゃないですかやだー!!
ということでしたというメモ。
------------------------------
コメント頂いて気づいたけども、「暗黙変換(継承先スマポ=>継承元スマポのような)」の場合は暗黙moveもされないし、もちろんNRVOもかからないので、std::moveを使うのはすげー特定場面でなく、単に特定場面くらいのニュアンスになりそう。
もちろん暗黙変換考える際にはポリモーフィックな感じに使う場面がメインと考えられて、moveされなくても誤差い場合が多いだろうけれども、そうでない場面も全然ありうるので忘れてはいけない場合でした。