LoginSignup
10
9

More than 5 years have passed since last update.

C++11でもmove on captureしたい!!!

Last updated at Posted at 2016-06-21

問題提起

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 を参考にしました。

10
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
9