読者です 読者をやめる 読者になる 読者になる

Boost.Context デストラクタの挙動

C++

継続のコンテキスト内で生成したオブジェクトのデストラクタはどういう挙動をするのか調べました。
まず、デストラクタが呼ばれないno_stack_unwindの場合:

#include <iostream>
#include <boost/context/all.hpp>
#include <boost/function.hpp>

class continuation {
    boost::contexts::context ctx_;
    boost::function<void(continuation&)> fn_;
    bool started_;

    void trampoline_()
    { fn_(*this); }

public:
    continuation(boost::function<void(continuation&)> const& fn) :
        ctx_(
            &continuation::trampoline_, this,
            boost::contexts::default_stacksize(),
            boost::contexts::no_stack_unwind, boost::contexts::return_to_caller),
        fn_(fn), started_(false)
    {}

    void resume()
    {
        if (!started_) {
            started_ = true;
            ctx_.start();
        }
        else {
            ctx_.resume();
        }
    }

    void suspend()
    { ctx_.suspend(); }

    bool is_complete() const
    { return ctx_.is_complete(); }
};

struct X {
    X()  { std::cout << "ctor" << std::endl; }
    ~X() { std::cout << "dtor" << std::endl; }
};

void f(continuation& cont)
{
    X x;

    for (int i = 0; i < 5; ++i) {
        std::cout << i << std::endl;
        cont.suspend();
    }
}

int main()
{
    std::cout << "start" << std::endl;
    {
        continuation cont(f);
        cont.resume(); // 0が出力される
    } // dtorが出力されない
    std::cout << "end" << std::endl;
}
start
ctor
0
end

no_stack_unwindの場合、コンテキストが破棄されるときにもデストラクタは呼ばれないようです。
contextのunwind_stack()メンバ関数を呼べばデストラクタが呼ばれます。


次にデストラクタが呼ばれるstack_unwindの場合:

#include <iostream>
#include <boost/context/all.hpp>
#include <boost/function.hpp>

class continuation {
    boost::contexts::context ctx_;
    boost::function<void(continuation&)> fn_;
    bool started_;

    void trampoline_()
    { fn_(*this); }

public:
    continuation(boost::function<void(continuation&)> const& fn) :
        ctx_(
            &continuation::trampoline_, this,
            boost::contexts::default_stacksize(),
            boost::contexts::stack_unwind, boost::contexts::return_to_caller),
        fn_(fn), started_(false)
    {}

    void resume()
    {
        if (!started_) {
            started_ = true;
            ctx_.start();
        }
        else {
            ctx_.resume();
        }
    }

    void suspend()
    { ctx_.suspend(); }

    bool is_complete() const
    { return ctx_.is_complete(); }
};

struct X {
    X()  { std::cout << "ctor" << std::endl; }
    ~X() { std::cout << "dtor" << std::endl; }
};

void f(continuation& cont)
{
    X x;

    for (int i = 0; i < 5; ++i) {
        std::cout << i << std::endl;
        cont.suspend();
    }
}

int main()
{
    std::cout << "start" << std::endl;
    {
        continuation cont(f);
        cont.resume(); // 0が出力される
    }
    std::cout << "end" << std::endl;
}
start
ctor
0
dtor
end

こちらはデストラクタが呼ばれています。
途中で終了してますが、1, 2, 3, 4が出力されてないので、空になるまでresumeしてるわけではないようです。


リソースを特に扱わない多くの場合には、no_stack_unwindで十分な気がします。
ファイルやネットワークでの継続的な読み取りとかの場合は、no_stack_unwindにしてクラスのメンバ変数でリソースを別管理するか、stack_unwindにして関数内で完結させるかどちらかを選択することになると思います。