Hatena::ブログ(Diary)

joynote break;

2011-08-22

[][] 暗黙の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されなくても誤差い場合が多いだろうけれども、そうでない場面も全然ありうるので忘れてはいけない場合でした。

gintenlabogintenlabo 2011/08/23 23:04 暗黙変換が絡む場合は move されないです:
http://d.hatena.ne.jp/gintenlabo/20110125/1295991902

std::shared_ptr<Base> f() {
auto p = std::make_shared<Derived>();
return p; // move させるには return std::move(p); と書く必要あり
}

幸い, std::vector のようなコピーコストの大きなクラスは,暗黙変換を用意していない場合がほとんどなので,そこまで問題にはならないと思いますが,ラッパークラスを書いた場合には注意するべきかと. もちろん暗黙変換が絡めば NRVO は発動しません.

joynotejoynote 2011/08/24 11:09 なるほど! 暗黙変換が行われる場合の事を失念していました。
ご指摘ありがとうございます。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

Connection: close