coiledcoilの日記 このページをアンテナに追加 RSSフィード

2010-12-24

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

メリー・クリスマス\(^o^)/

C++ Advent Calendar jp 2010 に参加してる一環として記事を書いてみました。


といっても、普段はPythonPHPばっかり触っているので、C++はあまりついて行けてなく・・・(汗)

でも、興味は人一倍あるんですが!好きな言語はもちろんC++ですww

そんなわけで、間違っているところがあれば、どうぞご指摘ください><;


Visual Studio 2010が発売されてしばらく経ちました。

追加されたC++の新機能の中に、Rvalue Referencesっていうのがあります。

最初は私もちんぷんかんぷんだったのですが、よく調べてみると、これは便利かも?


使い道のひとつとして、オブジェクトの寿命の管理に使うと便利じゃないか??

Rvalue Referencesって紹介されるときに、オブジェクトのコピーの回数が減って高速になるんだよっていう効能が挙げられていることが多いのですが、それだけじゃないよね。


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

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

#include <vector>
#include <memory>
#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>

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

private:
    std::vector<unsigned char> 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 <vector>
#include <memory>
#include <boost/thread.hpp>

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<unsigned char> 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<image_buffer> 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にならないうちに、次の方に譲りたいと思います。

みなさん、よいクリスマスを。

gintenlabogintenlabo 2010/12/24 21:36 明示的に move ctor を定義すれば大丈夫だと思いますよ。 > thread_main を noncopyable に

gintenlabogintenlabo 2010/12/24 21:46 こんな感じですね。
http://ideone.com/mYvZ2
本来なら move ctor も書かなくていいんですが、コンパイラのバージョンによってはコンパイル通らないので明示的に書いています。

coiledcoilcoiledcoil 2010/12/30 12:46 コメントありがとうございます!!
なるほど、明示的に書けばいいんですね・・・。
ちょっと年末でばたばたしているので、手元で試せたら、また報告します!!

coiledcoilcoiledcoil 2011/01/05 10:34 ご指摘のmove ctorをつけて、Boost 1.45を使うとVisual Studio 2010でも、ちゃんとビルドできました。
ありがとうございます!!

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


画像認証

トラックバック - http://d.hatena.ne.jp/coiledcoil/20101224/1293186441