More than 1 year has passed since last update.

例外の転送とは

C++を含む多くの言語では、子スレッドの例外を親が拾う事は出来ない
スレッド毎に例外のコンテキストが別なので、他のスレッドで例外を拾うには
例外を転送しなければならない

子スレッドの例外は親でcatch出来ない
#include <iostream>
#include <thread>
#include <exception>


int main(){

  try{
    std::thread t([](){
      try{
        throw std::exception();    
      }catch(...){
        std::cout << "catch child" << std::endl;
        throw;
      }
    });


    t.join();

    }catch(...){
        std::cout << "catch parent" << std::endl;
    }

    return 1;
}

catch child
terminate called after throwing an instance of 'std::exception'

親で例外をハンドル出来ないので実行時エラーで落ちる。

boost.asioのコルーチンでは?

boost.asioはコルーチンを使うことが出来る。軽量スレッド。とても優秀
しかし このコルーチンも、例外を転送できなかった

boost.asioのコルーチン
#include <iostream>
#include <exception>

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/bind.hpp>

using namespace std;
using namespace boost::asio::ip;

int main(){
    boost::asio::io_service io_service;

        try {
            boost::asio::spawn( io_service, [](boost::asio::yield_context yield ) {

                try {
                    throw std::exception();
                } catch (...) {
                    cout << "catch child" << endl;
                    throw;
                }
            });

        } catch (...) {
            cout << "catch parent" << endl;
        }


        boost::asio::signal_set signals(io_service);
        signals.add(SIGINT);
        signals.add(SIGTERM);
#if defined(SIGQUIT)
        signals.add(SIGQUIT);
#endif // defined(SIGQUIT)
        signals.async_wait(boost::bind(
                &boost::asio::io_service::stop, &io_service));

        io_service.run();

    return 1;
}

spawnで、コンテキスト yieldを作成し、ラムダ関数でコルーチン実行している
下のspawn以下は この際無視して大丈夫

上のspawnで、ラムダ内で例外を発生させ、spawnの前後でcatchしようとしているが
これも上記と同じく、子供の例外を親が受け取れない

これは恐らく、コルーチンの実装もスレッドと同じく、例外コンテキストが個別になっているからだと思われる

例外を転送する(thread)

C++で例外を転送すには std:: exception_ptrを使います

exception_ptr(thread)

#include <iostream>
#include <thread>
#include <exception>
#include <stdexcept>



int main(){

  std::exception_ptr ep;

  try{
    std::thread t([&ep](){
      try{
        throw std::exception();    
      }catch(...){
        std::cout << "catch child" << std::endl;
        ep = std::current_exception();
      }
    });


    t.join();
    if(ep){
      std::rethrow_exception(ep);
    }

  }catch(...){
    std::cout << "catch parent" << std::endl;
  }

    return 1;
}

このようにすると

catch child
catch parent

と、親に例外を転送できました!!

boost.asio コルーチンで行う

やることは同じですが、asioは非同期に設計さていて、spawn後に関数抜けてしまうので
無理やりですが 確認のために 無限ループコルーチンを作り
そこで確認してみます。。。

コルーチン
#include <iostream>
#include <exception>

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/bind.hpp>
#include <stdexcept>

using namespace std;
using namespace boost::asio::ip;

int main(){
  boost::asio::io_service io_service;

  std::exception_ptr ep;


    boost::asio::spawn( io_service, [&ep](boost::asio::yield_context yield ) {

        try {
            throw std::exception();
        } catch (...) {
            cout << "catch in" << endl;
            ep = std::current_exception();
        }
    });

    // 無限ループ
    boost::asio::spawn(io_service, [&ep,&io_service](boost::asio::yield_context yield) {

        try {
            boost::asio::deadline_timer timer(io_service);
            for (; ;) {
                timer.expires_from_now(boost::posix_time::millisec(100));
                timer.async_wait(yield);
                if(ep){
                  std::rethrow_exception(ep);
                }


            }
        }catch(...){
            std::cout << "catch other" << std::endl;
            return;
        }
    });


    boost::asio::signal_set signals(io_service);
    signals.add(SIGINT);
    signals.add(SIGTERM);
#if defined(SIGQUIT)
    signals.add(SIGQUIT);
#endif // defined(SIGQUIT)
    signals.async_wait(boost::bind(
            &boost::asio::io_service::stop, &io_service));

    io_service.run();

    return 1;
}

catch child
catch other

例外が転送できましたね。

非同期になると、親で受け取りにくくなるため
使いにくい気もしますが
一応これで例外を転送できます