【问题标题】:Replace C macro for a function call with a C++ template?用 C++ 模板替换函数调用的 C 宏?
【发布时间】:2016-03-26 05:13:42
【问题描述】:

是否可以替换这个预处理器宏:

#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可以是什么类型的?
  • a 是什么让声明 a; 有意义?在不知道的情况下很难回答这个问题。也许您的意思是a();,它是一个函数指针?如果是这种情况,那么只需要一个简单的 inline-functon,除非您需要具有不同签名的函数。
  • 这真的是一个c宏吗? (UtilitySoundNode::printALError!)
  • @user3490458 :我猜他的意思是一个 C 预处理器宏——你可以把任何你喜欢的东西放在宏中,就预处理器而言,预处理器不检查语法,这是由展开后的编译器。我编辑了文本和标签以更正它。
  • @AnonMail 是一个函数调用。很抱歉没有指出这一点。

标签: c++ templates macros c-preprocessor


【解决方案1】:
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__)

而且几乎是替代品。

【讨论】:

  • (当然意味着扩展为正确的值,它们确实会扩展,但AFAIK不是调用者文件和行)
  • @Yakk,不幸的是,我必须支持 GCC 3.x.x,所以 Lambdas 和这些现代的东西是不可能的。那么有没有不需要 C++11 或 boost 的解决方案呢?
【解决方案2】:

这里的一个问题似乎是“a”可以是一些任意函数(带有参数),它设置 alGetError() 返回的错误代码。

可以使用仿函数对象将其重写为 C++。要传递参数(和对象实例,如有必要)可以使用std::bindboost::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,(这不是对我的投票的评论)。
  • std::bind 是否会产生任何开销?您会在性能关键代码(例如渲染循环)中使用它(std::bind)和该模板吗?作为替代方案,我开始考虑使用goto 进行错误处理(尽管我不太确定它是否会起作用)。
  • @axalis 不幸的是,我需要通过任何 C++11 功能或提升。
【解决方案3】:

请注意,使用模板代替宏不会产生精确的模拟。您问题中定义的宏允许 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 ("could not deduce template argument for 'T' from 'void'"),这似乎不起作用 - 这就是我在回复中使用 std::bind() 的原因。
  • @E.Maskovsky:我的回答已经说明了我的假设。您的解决方案仍然不能解决原始宏允许参数是语句(例如,if 语句)的一般问题。
  • 哦,抱歉,没注意到。是的,宏还允许表达式,这是 std::bind 解决方案所不允许的(但这可以通过在支持宏中使用 lambda 来解决)
【解决方案4】:

不,__FILE____LINE__ 的使用需要预处理器。

【讨论】:

    猜你喜欢
    • 2014-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-17
    相关资源
    最近更新 更多