問題提起
C++11で、クロージャを使用する際に、unique_ptr
をキャプチャしたい、ということもあるのではないでしょうか。
もしかしたら当たり前のことなのかもしれませんが、C++11でそれを達成するための方法を書きました。
こんなコードがあったとします。
hogeクラスは、コンストラクタで受け取った文字列をメンバに持っておき、funcメソッドはその文字列を出力します。
worker::wokerメソッドは引数で受け取ったhogeオブジェクトのfuncメソッドを3秒後に呼び出すスレッドを作成し、
uniqur_ptr<thread>
を返します。
#include <iostream>
#include <memory>
#include <utility>
#include <thread>
using namespace std;
// コンストラクタで渡された文字列を出力するだけのメソッドfuncを持つ
class hoge {
public:
hoge(string data):_data(data) {};
void func() {
cout << "message :" + _data << endl;
};
private:
string _data;
};
// workメソッドで渡されたhogeオブジェクトのfuncメソッドを
// 3秒後に呼ぶスレッドを立て、スレッドオブジェクトを返す
class worker {
public:
worker() {};
unique_ptr<thread> work(unique_ptr<hoge> hoge) {
unique_ptr<thread> th(new thread([&hoge]{
this_thread::sleep_for(chrono::seconds(3));
hoge->func();
}));
return th;
};
};
int main(int argc, char const* argv[])
{
unique_ptr<hoge> hoge_ptr(new hoge("hello!!"));
unique_ptr<worker> worker_ptr(new worker());
// workはthreadオブジェクトを返すのでjoinしてあげる
unique_ptr<thread> thread_ptr(worker_ptr->work(move(hoge_ptr)));
thread_ptr->join();
return 0;
}
残念ながら、このプログラムはsegmentation faultエラーを引き起こします。
私の環境での出力は以下のとおりです。
$ ./a.out
zsh: segmentation fault (core dumped) ./a.out
原因はworker::workにあります。
unique_ptr<thread> work(unique_ptr<hoge> hoge) {
// unique_ptr<hoge>型のhogeを参照で受け取っているが、
// 3秒後にはhogeのデストラクタが呼ばれている
unique_ptr<thread> th(new thread([&hoge]{
this_thread::sleep_for(chrono::seconds(3));
hoge->func();
}));
return th;
};
参照で受け撮ったhogeが3秒後には生きていないのです。
さて、どうしましょう。
解決方法1 C++14を使って move on captureする
C++14で追加された「初期化lambdaキャプチャ」を使います。
スレッドオブジェクトを生成している部分をこのように書き換えましょう。
unique_ptr<thread> th(new thread([hoge = move(hoge)]{
this_thread::sleep_for(chrono::seconds(3));
hoge->func();
}));
キャプチャ指定子の中でmoveすればOKです。
C++11でなんとかする
しかし、何らかの理由でC++14が使えない場合、当たり前ですが初期化lambdaキャプチャを使うことはできません。
そこで、以下のようなクラスを追加します。
template<class T>
class move_on_copy {
public:
move_on_copy(T&& value) : _value(move(value)){};
move_on_copy(const move_on_copy& other): _value(move(other._value)) {};
const T& get() const { return _value; };
T& get() { return _value; };
private:
mutable T _value;
move_on_copy& operator=(const move_on_copy<T>& other) = delete;
move_on_copy& operator=(const move_on_copy<T>&& other) = delete;
};
このクラスのインスタンスがコピーされた時、メンバ変数である_value
はコピー先のインスタンスの_value
にmoveされます。
unique_ptr<hoge>
を渡したこのクラスのインスタンスをキャプチャすると、コピーコンストラクタが呼び出され、
無事、クロージャの中でunique_ptr<hoge>
が使用できるという仕組みです。
この変更をwork
メソッドに適用すると、以下のようになります。
unique_ptr<thread> work(unique_ptr<hoge> hoge_ptr) {
move_on_copy<unique_ptr<hoge>> hoge_container(move(hoge_ptr));
unique_ptr<thread> th(new thread([hoge_container]{
this_thread::sleep_for(chrono::seconds(3));
hoge_container.get()->func();
}));
return th;
};
実行結果は以下のとおりです。
$ ./a.out
message :hello!!
参考サイト
この記事を書くにあたり、Learn how to capture by move を参考にしました。