自定义`assert`宏,支持逗号和错误消息。

5
我希望创建一个自定义版本的<cassert>宏的,当断言失败时显示错误消息。

期望使用方法:

custom_assert(AClass<T1, T2>::aBoolMethod(), "aBoolMethod必须为true");


错误的测试实现:

#define custom_assert(mCondition, mMessage) ...
// This fails because mCondition may have commas in it

#define custom_assert(..., mMessage)
// Not sure about this either - mMessage may be an expression containing commas
// as well

我该如何正确实现一个自定义的断言,它将布尔表达式(可能带有逗号)作为第一个参数,将字符串表达式(可能带有逗号)作为第二个参数?

或者有没有一种方法可以在不使用宏的情况下实现断言?


你必须有一种区分消息和布尔值的方式。它是什么? - elyashiv
尝试使用此链接:https://dev59.com/Qm865IYBdhLWcg3wivGi - Nikos Athanasiou
1
C预处理器不了解C++模板。在这种情况下,您需要在第一个参数周围添加额外的一组括号,以使其正确解析,例如:custom_assert((AClass<T1, T2>::aBoolMethod()), "aBoolMethod must be true"); - Adam Rosenfield
6
为什么必须使用宏?为什么不将其制作成函数? - dyp
1
我看到的一种方法是 assert(expr && "some text");,可以使用支持带逗号的表达式的 custom_assert() 函数来实现,其实现非常简单,可以使用 #define custom_assert(...) assert((__VA_ARGS__))。这对你们来说是否足够呢?如果您确实需要将要检查的表达式与字符串分开,请将该字符串作为第一个参数,使用 #define custom_assert(msg, ...) <...>(其中再次使用 __VA_ARGS__ 表示表达式,可能包含逗号)会更容易实现。 - user743382
显示剩余4条评论
4个回答

4
你的理解很接近,你只需要使用这个简单的方法就可以了:

你需要做的就是:

#define myAssert(message, ...) do { \
    if(!(__VA_ARGS__)) { \
        /*error code*/ \
    } \
} while(0)

特殊的预处理器变量__VA_ARGS__将扩展为在三个点的位置传递的任何内容,包括所有逗号。
请注意,预处理器不会解释条件语句中的逗号,它只会将它们原样粘贴到if()语句中。如果你想传递模板化的条件,那么这正是你想要的,正如注释所示。
消息字符串中的逗号也不是问题,因为预处理器理解字符串字面值,并且不会解释双引号中的任何内容。

啊,我完全误解了你的目标,抱歉。我已经相应地更新了答案。 - cmaster - reinstate monica
1
+1,这基本上就是我在评论中建议的,但你已经将其解决为完整的答案。不过需要注意的一点是,OP也担心message中包含逗号。我不认为这会成为问题(预处理器将把所有字符串字面量,即使其中包含逗号,视为单个标记),但如果您认为有问题,最好在您的答案中解决这个问题。 - user743382
@hvd 已完成。感谢您的提示。 - cmaster - reinstate monica
1
该消息也可以是使用逗号的模板表达式 - 我想唯一的解决方案是将表达式用括号括起来。 - Vittorio Romeo

1

这个直观的

assert(AClass<T1, T2>::aBoolMethod() && "aBoolMethod must be true");

失败了:

错误:宏“assert”传递了2个参数,但只需要1个

但如果在第一个参数周围添加额外的括号,它就可以工作。像这样:

#include <cassert>

template <typename A, typename B>
struct C { 
  bool f() { return false; }
};

int main() {
  assert((C<int,int>().f()) && "some message, with comma");
  //     ^                ^
}

注意:Adam Rosenfield 在评论中也指出了这一点。
也许唯一的好处是,它不会在用户上再次添加宏。如果您忘记括号,可能会导致编译时错误,因此我认为它是一种安全的解决方案,与__VA_ARGS__方法相比。

1

为了完整起见,我在C++中发布了一个可插入的2个文件assert宏实现:

#include <pempek_assert.h>

int main()
{
  float min = 0.0f;
  float max = 1.0f;
  float v = 2.0f;
  PEMPEK_ASSERT(v > min && v < max,
                "invalid value: %f, must be between %f and %f", v, min, max);

  return 0;
}

会提示您:
Assertion 'v > min && v < max' failed (DEBUG)
  in file e.cpp, line 8
  function: int main()
  with message: invalid value: 2.000000, must be between 0.000000 and 1.000000

Press (I)gnore / Ignore (F)orever / Ignore (A)ll / (D)ebug / A(b)ort:

在哪里

  • (I)gnore:忽略当前断言
  • Ignore (F)orever:记住断言触发的文件和行,并在程序的剩余执行期间忽略它
  • Ignore (A)ll:忽略所有剩余的断言(所有文件和行)
  • (D)ebug:如果已连接调试器,则进入调试器,否则调用abort()(在Windows上,系统将提示用户附加调试器)
  • A(b)ort:立即调用abort()

您可以在此处了解更多信息:

希望这有所帮助。


0

我不太确定你所说的“带逗号的布尔表达式”的含义。简单地在宏展开周围加上逗号(如下面的示例所示)可以防止在条件中使用默认逗号运算符时出现解析错误,但是默认的逗号运算符与 && 的作用不同。如果你想要类似于这样的东西:

my_assert(condition1, condition2, message1, message2);

你们运气不好。任何 API 怎么可能知道条件结束,消息开始呢?直接使用 && 即可。你可以使用下面的技巧来处理消息部分,也可以创建一个 my_condition_set 宏,让你可以写出如下代码:

my_asssert(my_condition_set(condition1, condition2), message1, message2);

进入消息部分,这是标准assert宏通常缺乏的棘手和最有用的部分,技巧将归结为使用覆盖operator,(逗号运算符)的自定义消息输出类型或使用可变参数模板。

该宏使用可变宏支持和一些技巧来处理逗号。请注意,我在此发布的代码都没有直接测试过;它们都来自我过去编写的自定义断言宏的记忆。

使用逗号运算符重载的版本,在C++98编译器中有效:

struct logger {
  template <typename T>
  logger& operator,(const T& value) {
    std::cerr << value;
    return *this;
  }
};

#define my_assert(condition, ...) do{ \
  if (!(condition)) { \
    (logger() , __VA_ARGS__); \
    std::terminate(); \
  } \
}while(false)

logger()表达式创建了logger类型的新实例。然后,从__VA_ARGS__中获取参数列表,并用逗号分隔每个参数。这些逗号按从左到右的顺序调用逗号运算符,将表达式转发到std::cerr。我通常使用自定义日志流来处理写入文件、cerr、Windows的OutputDebugStringA或其他操作。

在C++11编译器中使用可变模板,代码会更像这样:

template <typename ...Ts>
void logger(Ts&&... argv) {
  std::cerr << your_string_format_function(argv...);
}

void logger(const char* fmt) {
  std::cerr << fmt;
}

void logger() {}

#define my_assert(condition, ...) do{ \
  if (!(condition)) { \
    logger(__VA_ARGS__); \
    std::terminate(); \
  } \
}while(false)

你需要一个能够处理可变参数的格式化字符串函数,或者编写适配器来使用像 boost::format 这样的工具。
如果你不介意缺乏类型安全性,也可以使用 printf

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