【问题标题】:C++11 lambdas passed as std::function params - dispatching on return type作为 std::function 参数传递的 C++11 lambda - 在返回类型上调度
【发布时间】:2012-03-13 17:13:15
【问题描述】:

场景:

我们有一个具有通用错误处理接口的 API。它是一个 C API,所以它告诉我们,在调用每个 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 调用,所以……宏中的宏?调试简直就是地狱。

我自己的尝试如下,使用 lambdas 和 std::function - 但它可以说是丑陋的...... 我无法将表达式的 lambda 直接传递给采用 std::function 的模板(根据 lambda 的返回类型进行专门化),因此代码变得相当讨厌。

你能想出更好的方法吗?

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__);

更新,KennyTM 优秀解决方案的附录

我放置了触发这个问题的实际 OpenGL 代码here。如您所见,错误检查代码不仅仅是打印 - 它还引发了用户代码可以处理的异常。我添加此附录以说明使用 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 的析构函数中的“抛出”是第一个——即实际调用的 C API 永远不会抛出。使用这种形式很容易做到这一点:

#define GLERROR(...)                                    \
    ([&]() -> decltype(__VA_ARGS__)                     \
    {                                                   \
        ErrorChecker _api_checker {__FILE__, __LINE__}; \
        (void) _api_checker;                            \
        try {                                           \
            return __VA_ARGS__;                         \
        } catch(...) {}                                 \
    } ())

这种形式的宏保证实际的 C API(通过 VA_ARGS 调用)永远不会抛出 - 因此,ErrorChecker 的析构函数中的“抛出”始终是第一个 这样做。

所以这个解决方案涵盖了我最初问题的所有角度 - 非常感谢 Alexander Turner 提供它。

【问题讨论】:

标签: c++ templates lambda c++11 std-function


【解决方案1】:

将日志记录代码放入某个类的析构函数中(假设记录器不会抛出异常),然后在宏中创建该类的实例。滥用逗号操作符,我们有:

struct Logger
{
    const char* file;
    int lineno;
    ~Logger()
    {
        // The APIerrorHappened stuff.
    }
};

#define APIERROR(...) (Logger{__FILE__, __LINE__}, (__VA_ARGS__))

演示:http://ideone.com/CiLGR

【讨论】:

  • 您已经对此发表了评论,但是如果错误处理程序抛出怎么办?我们不能从 Logger 的析构函数中抛出 - 关于如何处理它的任何想法?
  • @ttsiodras :您可以向Logger 的构造函数传递一个在异常情况下应调用的回调;析构函数会在 try/catch 中调用它。当然,该回调也应该是 noexcept。
  • @ttsiodras:您的代码在commonCode 抛出时有效(但在expr 抛出时无效)。你能做到吗commonCodenoexcept
  • @KennyTM:鉴于commonCode正在做错误处理,它在stderr中报告错误,然后抛出异常是很正常的......这是我的actual code记住(OpenGL 的东西)
猜你喜欢
  • 1970-01-01
  • 2016-12-29
  • 2016-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多