エラーハンドリング勉強会(リンク切れ: http://partake.in/events/9874b92a-4cf0-4a20-a3fe-951239da5612)にて、時間が余っているということで飛び入り発表していました。こんなことなら、初めから参加者として名乗りを上げておけば良かったです。
内容としては、C++11のexception_ptrの使い方を簡単に話しました。そのとき画面に映していたのはこれです: exception_ptr in エラーハンドリング勉強会。
exception_ptrは「ライブラリを作るためのライブラリ」だと私は考えています。使わずに済むならそれに越したことはないです。とは言ったものの、ここではじかにexception_ptrを扱っていますけど。
以下は、そのとき発表した内容を基に、新しく書き起こしたものです。そのため、発表内容(「exception_ptr in エラーハンドリング勉強会」のスライド)とは厳密に一致していません。
さて、何か適当な処理を別のスレッドで行うという状況を考えましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 適当な関数 void f(int& result) { // ここで時間のかかる処理 result = 42; } int main() { int result; std::thread t(f, std::ref(result)); t.join(); std::cout << result << std::endl; } |
最後に、呼出し元(のスレッド)で結果を受け取っていますね。いつもうまくいくのであればよいのですが、場合によっては例外を投げるのだとしたら、どうしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class MyException {}; // 適当な関数 void f(int& result) { // ここで時間のかかる処理 int test = (rand() % 3); if (test == 0) { // 処理がうまくいかなければ例外を投げるかもしれない throw std::runtime_error("だめでした"); } else if (test == 2) { throw MyException(); } result = 42; } |
困ったことに、C++は例外で投げるオブジェクトの型に大きな制限がありません。極端な例では、intのような組込型でも可能です。Javaや.NETのように、特定の基底クラスから派生していること(かつ参照でオブジェクトを扱う)という条件の下であれば、単にExceptionやThrowable型のオブジェクトとして持ち運べばよいのですが、C++ではそうはいかないのです。
そこで、exception_ptrの出番です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | void f(int& result, std::exception_ptr& ep) { try { ep = nullptr; // ここで時間のかかる処理 int test = (std::rand() % 3); if (test == 0) { // 処理がうまくいかなければ例外を投げるかもしれない throw std::runtime_error("だめでした"); } else if (test == 2) { throw MyException(); } result = 42; } catch(...) { ep = std::current_exception(); } } |
このように、current_exception関数で例外オブジェクトをexception_ptrに捕まえます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | int main() { int result; std::exception_ptr ep; std::thread t(f, std::ref(result), std::ref(ep)); t.join(); try { if (ep != nullptr) { std::rethrow_exception(ep); } std::cout << result << std::endl; } catch(std::exception& e) { std::cerr << e.what() << std::endl; } catch(MyException const& e) { std::cerr << "だめだった" << std::endl; } } |
そして、rethrow_exceptionを使うとexception_ptrの中身をthrowできます。これをcatchすることで、中身を取り出せるというわけです。
最後に、これを小綺麗にラップしてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // 適当な関数 int f() { // ここで時間のかかる処理 int test = (std::rand() % 3); if (test == 0) { // 処理がうまくいかなければ例外を投げるかもしれない throw std::runtime_error("だめでした"); } else if (test == 2) { throw MyException(); } return 42; } int main() { async<int> res(f); // 何かほかの処理 try { std::cout << res.get_result() << std::endl; } catch(std::exception const& e) { std::cerr << e.what() << std::endl; } catch(MyException const& e) { std::cerr << "だめだった" << std::endl; } } |
こんな風になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | template<typename Result> class async { public: template<typename F> async(F f) : t(invoker, std::ref(result), std::ref(ep), f) {} Result get_result() { t.join(); if (result) { return *result; } else { std::rethrow_exception(ep); } } private: std::thread t; boost::optional<Result> result; std::exception_ptr ep; static void invoker( boost::optional<Result>& result, std::exception_ptr& ep, std::function<Result ()> f) { try { result = f(); } catch(...) { ep = std::current_exception(); } } async(async const&) = delete; async& operator =(async const&) = delete; }; |
もっとも、こういう「適当な関数を渡したら、新しくスレッドを作ってそこで実行してくれる」というようなものは、packaged_taskとunique_futureで可能のですので、実際にはそっちを使いましょう(参考: N2709 – 非同期実行のための Packaging Tasks – melpon日記 – C++すら(ry)。こいつらはC++11を待たずともBoostにもあります(Futures — Boost 1.47.0)。
なお、exception_ptr自体も、Boostに一応あります (exception_ptr — Boost 1.47)。ただし、とくにcurrent_exceptionがライブラリだけでは実現不可能なので、throwの代わりにboost::throw_exception関数を使わなければならないなどといった制限があります。std::exception_ptrが使える環境では、std::exception_ptrを使うことをおすすめします。
2016年11月23日追記:partake.inがサービスを終了し、ページが消滅したので、リンクを解除しました。
スポンサード リンク |
この記事のカテゴリ
- C++ ⇒ exception_ptr in エラーハンドリング勉強会
boost::current_exception()を使用するに際してboost::throw_exception()/BOOST_THROW_EXCEPTION()を使わなければならないという制限はないです。
ただ、exception_ptrとして返された物の精度というか完全性はないです。
あと、boost::diagnostic_informationに投げても素っ気無いwhat()の結果しか返してくれない程度でしょうか。
そうでした、ありがとうございます。boost::current_exceptionの中で、せっせとcatchを並べて、できる限り捕捉しようとしているコードを見た覚えがあります。