シナリオ:
一般的なエラー処理インターフェースを備えたAPIがあります。これはCAPIであるため、API関数が呼び出されるたびに、次のような定型文を実行する必要があることがわかります。
if (APIerrorHappened()) {
APIHandle h;
while(h = APIgetNextErrorContext(...)) {
cerr << APIgetText(h) << endl;
}
}
あなたは明らかに自分自身を繰り返すのが嫌いなので、この処理を次のようなコードを記述できるマクロにカプセル化する必要があります。
...
// this API call returns something
APItype foo = MYMACRO(APIsomeFunction1(...));
// this one doesn't
MYMACRO(APIsomeFunction2(...));
// neither does this (glCompileShader)
MYMACRO(APIsomeFunction3(...));
...
アスペクト指向プログラミングの観点からもこれを考えることができます-マクロがロギングを追加し、リモートモニターに情報を送信することを想像してください...要点は、式をカプセル化し、その周りで何でもして、戻ることになっているということです式が返すタイプは何でも-そしてもちろん、式は何も返さないかもしれません。
つまり、あなたはただすることはできません
#define MYMACRO(x) { auto x = expr(); ... }
...場合によっては、式が何も返さないためです。
それで...どうしますか?
マクロ内にステートメント全体をカプセル化することを提案しないでください...
#define MYMACRO(x) \
{ \
/* ... stuff ... */ \
x; \
// ... stuff
}
...これは次のようなものでは機能しないため:
if (foo() || APIfunctionReturningBool(...) || bar()) {
...
APIfunction1();
...
} else if (APIOtherfunctionReturningBool() || baz()) {
...
APIfunction2();
...
}
...あなたはすべてのifステートメントを飲み込みますか?そのアクションには他のAPI呼び出しが含まれているので、マクロ内のマクロ?デバッグは地獄になりました。
ラムダとstd::functionを使用した私自身の試みは以下のとおりですが、間違いなく醜いです...式のラムダをstd :: functionを使用するテンプレートに直接渡すことはできませんでした(ラムダの戻り値に基づいて特殊化するため)タイプ)なので、コードはかなり厄介であることがわかりました。
もっと良い方法を考えられますか?
void commonCode(const char *file, size_t lineno) {
// ... error handling boilerplate
// ... that reports file and lineno of error
}
template <class T>
auto MyAPIError(std::function<T()>&& t, const char *file, size_t lineno) -> decltype(t()) {
auto ret = t();
commonCode(file,lineno);
return ret;
}
template<>
void MyAPIError(std::function<void(void)>&& t, const char *file, size_t lineno) {
t();
commonCode(file,lineno);
}
template <class T>
auto helper (T&& t) -> std::function<decltype(t())()>
{
std::function<decltype(t())()> tmp = t;
return tmp;
}
#define APIERROR( expr ) \
return MyAPIError( helper( [&]() { return expr; } ), __FILE__, __LINE__);
UPDATE、KennyTMの優れたソリューションの補遺
この質問をトリガーした実際のOpenGLコードをここに配置しました。ご覧のとおり、エラーチェックコードは単に印刷するだけでなく、ユーザーコードが処理できる例外もスローしました。この補遺を追加して、KennyTMのソリューションでは、デストラクタからこの例外をスローすることになり、これで問題がないことに注意してください(続きを読む)。
struct ErrorChecker {
const char *file;
size_t lineno;
~ErrorChecker() {
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
while (err != GL_NO_ERROR) {
std::cerr <<
"glError: " << (char *)gluErrorString(err) <<
" (" << file << ":" << lineno << ")" << std::endl;
err = glGetError();
}
throw "Failure in GLSL...";
}
}
};
このデストラクタからスローしても問題がない理由は、C++FAQで説明されています。
C ++の規則では、別の例外の「スタックアンワインド」プロセス中に呼び出されているデストラクタから例外をスローしてはなりません...別の例外の処理中にデストラクタから例外をスローしないと言うことができます。
この場合、(特別なマクロを呼び出す)ユーザーコードで例外を処理する必要があります。したがって、ErrorCheckerのデストラクタでの「スロー」が最初のものであること、つまり、呼び出された実際のCAPIがスローできないことを確実に知る必要があります。これは、次のフォームで簡単に実行できます。
#define GLERROR(...) \
([&]() -> decltype(__VA_ARGS__) \
{ \
ErrorChecker _api_checker {__FILE__, __LINE__}; \
(void) _api_checker; \
try { \
return __VA_ARGS__; \
} catch(...) {} \
} ())
この形式のマクロは、実際のC API(VA_ARGSを介して呼び出される)がスローされないことを保証します。したがって、ErrorCheckerのデストラクタの「スロー」が常に最初にスローされます。
したがって、このソリューションは私の元の質問のすべての角度をカバーします-それを提供してくれたAlexanderTurnerに感謝します。