使用C++模板来替换C宏函数调用?

3

能否替换这个预处理宏:

#define AL_CALL(a) do { a;                              \
                        ALenum e = alGetError();        \
                        if(e != AL_NO_ERROR)            \
                            UtilitySoundNode::printALError(e,__FILE__, __LINE__); \
                       } while(0)

使用C++模板可以处理这种情况吗?如果可能的话,这样做有意义吗(利弊 - 开销/调试)?

注意: 基本上,我想知道在C++中是否有一种优雅的方式来处理这种错误处理。

编辑: 当然,我犯了一个错误a是一个函数调用。正如人们可能猜到的那样,它是一个带有OpenAL函数参数的函数调用。

AL_CALL(someAlFunction(param1, param2))

注意:有人决定编辑宏并使其更加美观,但我仍然喜欢保留原来的宏。因此,在这里它依然存在:

#define AL_CALL(a) {a; ALenum e = alGetError();if(e != AL_NO_ERROR)PUtilitySoundNode::printALError(e,__FILE__, __LINE__);}

a可以是什么类型? - Anon Mail
什么样的a会使得语句a;有任何意义呢?如果不知道具体情况,很难回答这个问题。也许你是指a();并且它是一个函数指针?如果是这种情况,那么只需要一个简单的内联函数就可以了,除非你需要具有不同签名的函数。 - Clifford
这真的是一个C宏吗?(UtilitySoundNode::printALError!) - Zaxter
1
@user3490458:我猜他指的是C预处理器宏 - 你可以在宏中放任何你喜欢的东西,就预处理器而言,它不会检查语法,这是在展开后由编译器完成的。我已经编辑了文本和标签来纠正这一点。 - Clifford
@AnonMail 这是一个函数调用。很抱歉没有提醒您。 - Zingam
4个回答

4
这里的一个问题似乎是"a"可能是一些任意的函数(带有参数),该函数设置由alGetError()返回的错误代码。
可以使用一个函数对象将其重写为C++。要传递参数(如果必要,还要传递对象实例),可以使用std::bind或boost::bind(请注意,要绑定引用参数,需要使用std::ref/boost::ref)。
但是,如果您仍然想让__FILE__和__LINE__传递给printError(),则该C++模板仍需要由宏调用,该宏将它们传递给模板。__FILE__和__LINE__仅由预处理器扩展,因此无法避免使用宏。
但是,该宏可以简单得多,并且大部分工作可以在C++模板中完成(这具有很多优点,例如对于调试,因为在大多数调试器中,您无法进入宏)。
编辑:将代码添加为示例:
template<typename T>
void ALcallAndCheck(T c, const char *file, size_t line)
{
    c();
    ALenum e = alGetError();
    if(e != AL_NO_ERROR)
        UtilitySoundNode::printALError(e, file, line); \
}

#define AL_CALL(a) ALcallAndCheck(a, __FILE__, __LINE__)

然后,不是使用
AL_CALL(SomeFunction(2, refAttr));

调用将变成:

AL_CALL(std::bind(SomeFunction, 2, std::ref(refAttr)));

编辑2: 前面的方法不适用于表达式,而原始宏允许使用表达式。为了也能处理表达式,可以对宏进行修改:

#define AL_CALL(a) ALcallAndCheck([&]{ (a); }, __FILE__, __LINE__)

这将创建一个lambda,它将评估进入宏的任何内容。然后即使没有std::bind也不需要,可以直接调用:

AL_CALL(SomeFunction(2, refAttr));
AL_CALL(SomeOtherFunction1()+SomeOtherFunction2(8));

很高兴你提到了 std::bind,(这并不是对我的点赞的评论)。 - jxh
std::bind会产生额外的开销吗?你会在性能关键的代码(例如渲染循环)中使用它(std::bind)和那个模板吗?作为一个替代方案,我开始考虑使用goto来处理错误(尽管我不太确定它是否有效)。 - Zingam
@axalis 很抱歉,我需要避免使用任何C++11的特性或boost库。 - Zingam

2
不,使用__FILE____LINE__几乎需要预处理器。

1
请注意,使用模板而不是宏并不能产生完全相同的效果。你问题中定义的宏允许a表示语句和表达式。模板没有那种灵活性。下面定义的模板假定a是一个非void表达式。
没有标准的方法可以在不调用方传递信息的情况下隐式注入函数调用者的文件名和行号。预处理器宏提供了一种方式使语法看起来像是隐式注入,实际上是通过传递信息来实现的。
template <typename T>
void AL_CALL (T a, const char *file, int line) {
    ALenum e = alGetError();
    if(e != AL_NO_ERROR)
        UtilitySoundNode::printALError(e, file, line);
}

#define AL_CALL(X) AL_CALL((X), __FILE__, __LINE__)

您可能能够使用特定于系统的工具(例如CaptureStackBackTrace + SymFromAddrbacktrace + backtrace_symbols)隐式地获取大致相同的信息,但可能需要存在调试符号,并且内联函数可能无法产生预期输出。


如果作为X参数传递的函数返回void(“无法从'void'推断出'T'的模板参数”),这似乎不起作用 - 这就是为什么我在我的回答中使用了std::bind() - EmDroid
@E.Maskovsky:我的回答已经说明了我的假设。你的解决方案仍然没有解决原始宏允许参数为语句(例如,一个if语句)的一般问题。 - jxh
哦,抱歉,没有注意到。是的,宏也允许使用表达式,而std::bind解决方案不允许(但可能可以通过在支持宏中使用lambda来解决)。 - EmDroid

0
template<class A>
void al_call(A&&a){
  ALenum e = a();
  if(e != AL_NO_ERROR)
    UtilitySoundNode::printALError(e,__FILE__, __LINE__);
}

使用:

al_call( [&]{ return bob(); });

不是:

AL_CALL( bob() )

上面的行/文件信息没有用。

所以

template<class A>
void al_call(A&&a, char const*file, unsigned line){
  ALenum e = a();
  if(e != AL_NO_ERROR)
    UtilitySoundNode::printALError(e,file, line);
}
#define AL_CALL(...) al_call([&]()mutable{return __VA_ARGS__;}, __FILE__, __LINE__)

而且它几乎是一个完美的替代品。


1
(当然,这些值将被展开为正确的值,但据我所知不会展开到调用者的文件和行) - EmDroid
@Yakk,很不幸的是,我必须支持GCC 3.x.x,所以Lambda表达式和其他现代特性都不可行。那么有没有不需要C++11或boost的解决方案呢? - Zingam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接