以下のコードでやっていることの説明.
EXCEPTION_HOOK((expr))
と書いておけば, (expr) という C++ の式を実行したら急に例外が来たので……というときに, call stack のようなもので殴られた跡 (この hook が仕掛けられている場所に関する情報) を boost::exception に乗せていきます. call stack の生成ぐらい言語でサポートして欲しいよね,うんうん.終わり. Microsoft Visual C++ 2010 と GCC 4.5.0 で動作を確認済みです.
工夫として,
EXCEPTION_HOOK((expr))
という文字列が式 expr と同等な C++ の expression として透過的に機能するように見せかけてあります.つまり,たとえば
std::string s("42"); int i = EXCEPTION_HOOK((boost:lexical_cast<int>(s)));
などと書けるようにしてあり,もちろん例外が通知されなければこのコードは
std::string s("42"); int i = boost::lexical_cast<int>(s);
と同じ挙動を示します.終わり.
透過性という点で重要な別の特徴として, preprocessing-time でこの hook を完全に無効化できます.無効化して,字句レベルで (expr) という構文として振舞わせるのは赤子の手をひねる (それをするなんてとんでもない!) よりも造作のないことよ,クックック…….
以下のコードの要点 ([&] な lambda-introducer を伴う lambda-expression を用いて, hook を仕掛けた場所のスコープの文脈を持ち込みつつコールバック化する点) さえ抑えておけば,任意の expression に対して,透過的に preprocessing-time, compile-time, runtime レベルの pre-hook, post-hook および例外送出時 hook を仕掛けるようなマクロ (「えーマジマクロ?」「マクロが許されるのは……」) を構築できるんじゃないんでしょうか,知らんけど?
細かいこと言うと hook を仕掛ける対象となる式の型が void でない場合は MoveConstructible でないといけないという制約がががが.ただし,過去,似たようなものはいくつか提示されていますが (Bjarne が昔書いていた operator-> で hook を書く記事どこいったっぽ? *1 ) ,「hook を仕掛ける構文がそのまま expression として透過的に扱える」かつ「例外送出イベントにも hook を仕掛けられる」というのを C++ で達成したものはなかったはずなのでにょほほほほ.
以下,とても長〜いコードと出力例.
#include <boost/config.hpp> #include <boost/preprocessor/stringize.hpp> #include <boost/type_traits/is_array.hpp> #include <boost/type_traits/is_function.hpp> #include <boost/type_traits/remove_cv.hpp> #include <boost/exception/error_info.hpp> #include <boost/exception/exception.hpp> #include <boost/exception/info.hpp> #include <boost/exception/get_error_info.hpp> #include <boost/lexical_cast.hpp> #include <string> #include <vector> #include <iostream> class hook_stack { private: class Element { public: Element(char const *function, char const *file, int line, char const *expression) : function_(function), file_(file), line_(line), expression_(expression) {} // Compiler-generated cctor, dtor and assignment are fine. char const *getFunction() const { return function_; } char const *getFile() const { return file_; } int getLine() const { return line_; } char const *getExpression() const { return expression_; } private: char const *function_; char const *file_; int line_; char const *expression_; }; public: hook_stack() : stk_() {} void push(char const *function, char const *file, int line, char const *expression) { stk_.push_back(Element(function, file, line, expression)); } friend std::ostream &operator<<(std::ostream &os, hook_stack const &x) { for (auto iter = x.stk_.begin(); iter != x.stk_.end(); ++iter) { os << "from\n"; os << " function : " << iter->getFunction() << "\n"; os << " file : " << iter->getFile() << "\n"; os << " line : " << iter->getLine() << "\n"; os << " expression: " << iter->getExpression() << "\n"; } return os; } private: std::vector<Element> stk_; }; // class hook_stack struct hook_stack_{}; typedef boost::error_info<hook_stack_, hook_stack> errinfo_hook_stack; namespace detail { /* 関数呼び出し構文・演算子の場合 T -> remove_cv<T>::type T & -> T & T && -> remove_cv<T>::type 関数呼び出し構文・演算子以外の場合 T & -> T & (lvalue) T -> remove_cv<T>::type (rvalue) */ template<class T> struct exception_hook_type_impl : public boost::remove_cv<T> { static_assert(!boost::is_array<T>::value, ""); static_assert(!boost::is_function<T>::value, ""); }; // struct exception_hook_type_impl template<class T> struct exception_hook_type_impl<T &&> : public boost::remove_cv<T> {}; template<class R, class F> R exception_hook_impl(char const *function, char const *file, int line, char const *expression, F f) try { return f(); } catch (boost::exception &e) { hook_stack *p = boost::get_error_info<errinfo_hook_stack>(e); if (!p) { e << errinfo_hook_stack(hook_stack()); p = boost::get_error_info<errinfo_hook_stack>(e); char const * *p_throw_function = boost::get_error_info<boost::throw_function>(e); char const * *p_throw_file = boost::get_error_info<boost::throw_file>(e); int *p_throw_line = boost::get_error_info<boost::throw_line>(e); p->push(!!p_throw_function ? *p_throw_function : "N/A", !!p_throw_file ? *p_throw_file : "N/A", !!p_throw_line ? *p_throw_line : 0, "N/A"); } p->push(function, file, line, expression); throw; } } // namespace detail // Workaround for MSVC (MSVC does not allow needless typename keywords) #if defined(BOOST_MSVC) #define EXCEPTION_HOOK(PARENTHESIZED_EXPRESSION) \ ::detail::exception_hook_impl< ::detail::exception_hook_type_impl<decltype(PARENTHESIZED_EXPRESSION)>::type>( \ __FUNCTION__, \ __FILE__, \ __LINE__, \ BOOST_PP_STRINGIZE(PARENTHESIZED_EXPRESSION), \ [&]() mutable -> ::detail::exception_hook_type_impl<decltype(PARENTHESIZED_EXPRESSION)>::type { return PARENTHESIZED_EXPRESSION; }) \ /**/ #else #define EXCEPTION_HOOK(PARENTHESIZED_EXPRESSION) \ ::detail::exception_hook_impl<typename ::detail::exception_hook_type_impl<decltype(PARENTHESIZED_EXPRESSION)>::type>( \ __FUNCTION__, \ __FILE__, \ __LINE__, \ BOOST_PP_STRINGIZE(PARENTHESIZED_EXPRESSION), \ [&]() mutable -> typename ::detail::exception_hook_type_impl<decltype(PARENTHESIZED_EXPRESSION)>::type { return PARENTHESIZED_EXPRESSION; }) \ /**/ #endif //////////////////////////////////////// // // End of library code. // //////////////////////////////////////// class C { private: int f() const { std::string s("nya-"); return EXCEPTION_HOOK((boost::lexical_cast<int>(s))); } public: void g() const { // Workaround for GCC (avoids the bug reported at http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43856) #if defined(__GNUC__) int i = EXCEPTION_HOOK((this->f())); #else int i = EXCEPTION_HOOK((f())); #endif } void h() const { // The same as above #if defined(__GNUC__) EXCEPTION_HOOK((this->g())); #else EXCEPTION_HOOK((g())); #endif } }; void g() { C c; EXCEPTION_HOOK((c.g())); } void h() { C c; EXCEPTION_HOOK((c.h())); } int main() { try { g(); } catch (boost::exception &e) { hook_stack *p = boost::get_error_info<errinfo_hook_stack>(e); if (!!p) { std::cout << *p << std::endl; } } std::cout << "========================================" << std::endl; std::string s("42"); std::cout << EXCEPTION_HOOK((boost::lexical_cast<int>(s))) << std::endl; std::cout << "========================================" << std::endl; try { h(); } catch (boost::exception &e) { hook_stack *p = boost::get_error_info<errinfo_hook_stack>(e); if (!!p) { std::cout << *p << std::endl; } } }
出力例 (Microsoft Visual C++ 2010 の場合)
from function : N/A file : N/A line : 0 expression: N/A from function : C::f file : d:\home\cryolite\visual studio 2010\projects\cpp0x_test\exception_hook\main.cpp line : 169 expression: (boost::lexical_cast<int>(s)) from function : C::g file : d:\home\cryolite\visual studio 2010\projects\cpp0x_test\exception_hook\main.cpp line : 178 expression: (f()) from function : g file : d:\home\cryolite\visual studio 2010\projects\cpp0x_test\exception_hook\main.cpp line : 195 expression: (c.g()) ======================================== 42 ======================================== from function : N/A file : N/A line : 0 expression: N/A from function : C::f file : d:\home\cryolite\visual studio 2010\projects\cpp0x_test\exception_hook\main.cpp line : 169 expression: (boost::lexical_cast<int>(s)) from function : C::g file : d:\home\cryolite\visual studio 2010\projects\cpp0x_test\exception_hook\main.cpp line : 178 expression: (f()) from function : C::h file : d:\home\cryolite\visual studio 2010\projects\cpp0x_test\exception_hook\main.cpp line : 187 expression: (g()) from function : h file : d:\home\cryolite\visual studio 2010\projects\cpp0x_test\exception_hook\main.cpp line : 201 expression: (c.h())
*1:追記:あった. リンク先 PDF 注意 http://www2.research.att.com/~bs/wrapper.pdf