#例外の転送とは
C++を含む多くの言語では、子スレッドの例外を親が拾う事は出来ない
スレッド毎に例外のコンテキストが別なので、他のスレッドで例外を拾うには
例外を転送しなければならない
#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はコルーチンを使うことが出来る。軽量スレッド。とても優秀
しかし このコルーチンも、例外を転送できなかった
#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を使います
#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
例外が転送できましたね。
非同期になると、親で受け取りにくくなるため
使いにくい気もしますが
一応これで例外を転送できます