Rvalue Referencesを使って「別のスレッドにオブジェクトの所有権を移動」を表現

メリー・クリスマス\(^o^)/
C++ Advent Calendar jp 2010 に参加してる一環として記事を書いてみました。


といっても、普段はPythonPHPばっかり触っているので、C++はあまりついて行けてなく・・・(汗)
でも、興味は人一倍あるんですが!好きな言語はもちろんC++ですww
そんなわけで、間違っているところがあれば、どうぞご指摘ください><;


Visual Studio 2010が発売されてしばらく経ちました。
追加されたC++の新機能の中に、Rvalue Referencesっていうのがあります。
最初は私もちんぷんかんぷんだったのですが、よく調べてみると、これは便利かも?


使い道のひとつとして、オブジェクトの寿命の管理に使うと便利じゃないか??
Rvalue Referencesって紹介されるときに、オブジェクトのコピーの回数が減って高速になるんだよっていう効能が挙げられていることが多いのですが、それだけじゃないよね。


例えば、オブジェクトがうっかりスレッド間で共有されることを防ぐことが出来ます。

実際に例を挙げてみます。

#include 
#include 
#include 
#include 

class image_buffer
{
public:
    explicit image_buffer(size_t sz) : buf_(sz) {}

private:
    std::vector buf_;
};

class thread_main
{
public:
	explicit thread_main(image_buffer& p)
		: p_(p)
	{
	}

	void operator()()
	{
	}

private:
	image_buffer& p_;
};

int main()
{
	image_buffer img(1 * 1024 * 1024);

	boost::thread th1((thread_main(img)));
	// boost::thread th2((thread_main(img)));
	th1.join();
	// th2.join();
	return 0;
}

この例で、image_bufferはコピーされると困るようなサイズの大きなクラスだとします。
さらになんらかの事情により、スレッド開始前にオブジェクトを作っておかないといけないとします。
それを引数として、thread_mainオブジェクトを作成し、スレッドを開始したいのですが、今までだと、この例のように参照として渡すか、ポインター(boost::shared_ptrなどのスマートポインター)として渡すしかなかったと思います。


しかし、どちらの方法でも、なんらかの手違いによりスレッド間でオブジェクトが共有されてしまう危険があります。
(上の例の中のコメントアウトを外しても、コンパイルが通ってしまいますよね。)


このような問題は、GCがあってオブジェクトを参照(C++でいうところのポインター?)で扱えるJavaC#であっても、同じように起こりえるのではないでしょうか。


Rvalue References をうまく使うと、オブジェクトが意図せず外部から共有されていないことをコンパイル時にチェックが出来ます。

実際に書き換えてみたのが、これ。

#include 
#include 
#include 

class image_buffer
{
public:
    explicit image_buffer(size_t sz) : buf_(sz) {}
	image_buffer(image_buffer&& rhs) : buf_(std::move(rhs.buf_)) {}

private:
    std::vector buf_;

private:
	image_buffer(image_buffer const&);
	image_buffer& operator=(image_buffer const&);
};

class thread_main
{
public:
	explicit thread_main(image_buffer&& img)
		: p_(new image_buffer(std::move(img)))
	{
	}

	void operator()()
	{
	}

private:
	std::shared_ptr p_;
};

int main()
{
	// image_buffer img(1 * 1024 * 1024);
	// boost::thread th1((thread_main(img)));
	// boost::thread th2((thread_main(img)));

	boost::thread th1(thread_main(image_buffer(1 * 1024 * 1024)));
	boost::thread th2(thread_main(image_buffer(1 * 1024 * 1024)));
	th1.join();
	th2.join();
	return 0;
}

thread_mainのコンストラクターがimage_buffer&からimage_buffer&&に変わっています。先ほどの例では、LvaluesからRValuesに変換しようとしますから、コメントアウトを外すとエラーになります。


書き換えてみて気づいたことですが、Rvalue Referencesはクラスのメンバー変数にしても、オブジェクトの寿命が延びないので意味がないのですね。かといって、コピーしてしまうと目的を達成できませんから、やむなくshared_ptrに入れています。
このあたり、もっといい方法がないものか。


さらに、thread_mainをnoncopyableにしてshared_ptrのかわりにunique_ptrにすると、なぜかコンパイルエラーに・・・(Visual Studio 2010)。
thread_mainはコピーできないとだめなんでしょうか。
このあたり、さらに調査が必要そう・・・。


それでも、thread_mainに対してオブジェクトを渡すときに、所有権を移動する(共有ではなく!)ということは実現できました。
下手に共有してしまうとまずいスレッド処理周りのオブジェクトのやりとりでは、Rvalue Referencesはかなり便利なんじゃないでしょうか。


単に値のコピーをふせいで高速化するだけが目的なら、GCがあれば不要なわけですが、オブジェクトの参照元を一カ所に限定するという機能はJavaなど、GCがある言語でも価値があるでしょう。
Rvalue References は面白い!


それでは、さらにgdgdにならないうちに、次の方に譲りたいと思います。
みなさん、よいクリスマスを。