便利かもしれないGCC builtin
これは恐らくC++ Advent Calendar 2014の15日目です。
古来[要出典]より人々はいかに例外を投げた奴をトレースするかに命をかけてきた[要出典]。
投げられた例外自体や、そのメッセージを確認することはできるが、一体誰がその例外を投げたかという情報は一切乗らないからだ。
いっそのことsegfaultでもしてくれたほうがデバッガでスタックトレースを出力できる。
ある人は考えるだろう。
template <typename Base> struct exception_info : Base { explicit exception_info(const Base &base, const char *file, int line) : Base(base), file(file), line(line) {} virtual ~exception_info() noexcept {} const char *file; int line; }; template <typename E> [[noreturn]] inline void throw_(const E &e, const char *file = __FILE__, int line = __LINE__) { throw exception_info<E>(e, file, line); } { throw_(std::runtime_error("hogefuga")); }
残念。__FILE__と__LINE__はプリプロセッサで展開されてしまうので、throw_がある行とファイルが例外に乗って飛んでくる。
正解は
template <typename E> [[noreturn]] inline void throw_(const E &e, const char *file, int line) { throw exception_info<E>(e, file, line); } #define THROW(e) throw_(e, __FILE__, __LINE__) { THROW(std::runtime_error("hogefuga")); }
さて、関数名もほしい。C++11なら__func__が使える。GCCなら__FUNCTION__(場合によっては__PRETTY_FUNCTION__かもしれない)だ。
catchする側は今度受け取った例外オブジェクトがexception_infoから派生しているか確認して、fileとか取れるなら表示したい。RTTIで分岐するしかない。
とまぁ、ここまで来ると正直Boost使いましょう*1*2ってなる。なれ。
#include <boost/throw_exception.hpp> #include <boost/exception/diagnostic_information.hpp> { BOOST_THROW_EXCEPTION(std::runtime_error("Boooooooooooooost")); } catch (std::exception &e) { std::cout << boost::diagnostic_information(e) << std::endl; }
まぁでも、結局Boostもだいたい同じようなことをやっているので、エラー食らった後にいろいろやって、結果、例外を投げる的な共通関数をつくった時にはいい感じにBoostも使えない(そのいい感じの共通関数を呼んだ奴が結局誰かわからない)。
関数呼び出しもマクロで囲めば良いんだけど何でもかんでもマクロなのはほげふが〜ってなってしまうので、もうちょっとスマートな方法を探したい。
そんな時にはGCC 4.9以降*3*4で使える次のbuiltin関数*5を使うと良いかもしれない。
- __builtin_LINE()
- __builtin_FILE()
- __builtin_FUNCTION()
それぞれ、__LINE__、__FILE__と__func__に対応している。
これらは関数であるのでプリプロセッサで展開はされないし、デフォルト引数として使えば呼んだ奴のコンテキストで評価される*6。
実際に使ってみるとこうなる。
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ
しかし自分でlineとか扱うとなると、先のBOOST_THROW_EXCEPTIONは使えないので、boost::throw_exceptionを呼ぶようにちょっと加工してやる。
template <typename E> [[noreturn]] inline void throw_(E &&ex, std::tuple<const char *, int, const char *> caller_ctx = std::make_tuple(__builtin_FILE(), __builtin_LINE(), __builtin_FUNCTION())) { boost::throw_exception( boost::enable_error_info(ex) << boost::throw_function(std::get<2>(caller_ctx)) << boost::throw_file(std::get<0>(caller_ctx)) << boost::throw_line(std::get<1>(caller_ctx))); }
これでnamespace下にthrow_を置いたり出来るし、もうちょっといろいろな関数を経由したりするときにはcaller_ctxを持ちまわってやれば良い。
私は更にbacktraceを乗せたりして使っているのでなにか参考になれば幸いだ。
shinano/exception.hpp at develop · Flast/shinano · GitHub
shinano/exception.cpp at develop · Flast/shinano · GitHub
あ、ちなみにclangは実装してないから。m9m9m9