【问题标题】:C++ Custom AssertC++ 自定义断言
【发布时间】:2016-01-26 15:58:50
【问题描述】:

我正在尝试为我自己的项目编写我自己的自定义断言。本项目将使用 c++11 编写。

断言必须具有以下品质:

它必须作为表达式保存并且是可赋值的。 例如。我应该可以写出这样的代码int x = custom_assert(1/y);

必须重载才能接受带有消息和不带消息的断言。 E.g int x = custom_assert(1/y, "Error divide by zero"); 这段代码和上面的代码都是可编译和可接受的。

在发布模式下必须没有副作用 例如。 int x = custom_assert(1/y); 在发布模式下会变成int x = 1/y;

最重要的是,它必须在断言的特定点中断。它将使用__debugbreak() 作为其评估表达式的一部分。

以下是我的尝试:

#include <string>

bool DoLog(std::string msg, std::string file, int line); //Prints to std::cerr and returns HALT macro


#if defined(_DEBUG) || defined(DEBUG)
#define HALT true
#define NT_ASSERT_BASE(x, msg) (!(x) && DoLog((msg), __FILE__, __LINE__) && (__debugbreak(),1))
#else
#define HALT false
#define NT_ASSERT_BASE(x,msg) (x)
#endif//debug/release defines

//--- Can't implement these until I can return the expression ---
//#define GET_MACRO(_1,_2,_3,NAME,...) NAME
//#define FOO(...) GET_MACRO(__VA_ARGS__, FOO3, FOO2)(__VA_ARGS__)

#define NT_ASSERT(expression, msg) NT_ASSERT_BASE(expression,msg)

如您所见,我的自定义断言在 2 个方面失败,即被保留为表达式和可分配,以及重载(在我弄清楚如何将其保留为表达式之前,我无法实现。

总而言之,我可能是在追星,这个宏实际上可能是不可能做出来的。 (我希望不是这样)

非常感谢。

【问题讨论】:

  • 被限制使用c++11/14?
  • 项目允许使用 C++11
  • 顺便说一下,在发布模式下保留的类断言表达式(减去实际断言)我相信称为"verify" expressions,尽管这可能是一个微软主义。总的来说,我认为 assert 代码保留在发布版本中会让人感到惊讶。
  • 这需要澄清一下。给定x = custom_assert(1/y);,触发“断言”的条件是什么?您的示例表明它被零除,但您的尝试表明它只是表达式评估为 0。

标签: c++ c++11 expression assert


【解决方案1】:

据我所知,这不能在标准 C++ 中完成

没有办法将__debugbreak() 放入扩展代码中,同时不加修改地传递表达式的结果,因为您需要两次结果:一次用于测试,这将隐式将其转换为@987654323 @,最后返回一次。

有两种选择:

  • 使用 gcc 和 clang 的 ({}) 构造和 auto 变量来保存结果。这将排除 MSC++,但我想你想要这样,因为 __debugbreak() 是 MSC++ 错误功能。

  • 放弃在调用站点上要求__debugbreak(),接受在停止时必须上一级并将事物作为模板函数。

    • lambda 表达式比模板函数更适合。它将使中断出现在宏站点上,但它仍会在调用堆栈中显示为单独的堆栈帧。它还需要 C++11 支持(它是 5 年前发布的,但某些平台可能没有)。

【讨论】:

  • lambda 不能这样做吗? [](bool b, const char* msg){ /* code */ }(param1, param2)
  • 我们在我的工作中使用了第二个建议,它工作正常;让调试器在检查表达式的模板函数中中断并不是那么糟糕;另一个技巧是在获得消息时懒惰地评估消息,这可以通过将其填充到 lambda 中来实现 - 我见过断言从未命中但在分析器中打印消息非常热的地方。
  • @StoryTeller,真的,我忘记了(我们仍然坚持使用 MSC++ 15.0,所以我不能在工作中使用它们)。 lambda 仍应在调用堆栈中显示为框架(至少在 Debug 构建中),但它将是代码清单中的正确位置。
  • @ChristopherLeong,既然你有 C++11,你也可以试试 lambda,但它仍然会显示为一个额外的堆栈帧。
  • 你为什么说__debugbreak() 是一个错误特征?
【解决方案2】:

我认为您不应该将验证与作业混为一谈。从您的示例中,您似乎想要分配一个整数,但断言本质上是一个布尔表达式。此外,您的示例断言错误的表达式。看起来您想要断言 y 不等于 0(防止被零除),但您要断言的东西也将是 1、false 或未定义。

如果您愿意灵活地处理分配要求,那么我们可以通过一些宏魔法来解决维护表达式和其他有用信息的问题。此外,我们可以在调用站点执行 __debugbreak()。

#include <iostream>
#include <string>
#include <type_traits>

template<class Fun>
inline bool DoLog(Fun f, std::string message, const char *expression, const char *filename, int line) {
    static_assert(std::is_same<bool, decltype(f())>::value, "Predicate must return a bool.");
    if (!(f())) {
        std::cerr << filename << '@' << line << ": '" << expression << "' is false.";
        if (!message.empty()) {
            std::cerr << ' ' << message;
        }
        std::cerr << std::endl;
        return false;
    }
    return true;
}

#if defined(_DEBUG) || defined(DEBUG)
#define HALT true

#define WITH_MESSAGE_(expr, x) [&](){return (expr);}, x, #expr
#define WITHOUT_MESSAGE_(expr) [&](){return (expr);}, std::string{}, #expr
#define PICK_ASSERTION_ARGS_(_1, _2, WHICH_, ...) WHICH_
#define CREATE_ASSERTION_ARGS_(...) PICK_ASSERTION_ARGS_(__VA_ARGS__, WITH_MESSAGE_, WITHOUT_MESSAGE_)(__VA_ARGS__)
#define NT_ASSERT(...) if (!DoLog(CREATE_ASSERTION_ARGS_(__VA_ARGS__), __FILE__, __LINE__)) __debugbreak()
#else
#define HALT false
#define NT_ASSERT(...)
#endif

int main() {
    NT_ASSERT(true);
    NT_ASSERT(false);
    NT_ASSERT(1 == 1, "1 is 1");
    NT_ASSERT(1 == 0, "1 is not 0");
    return 0;
}

注意:上面的 sn-p 在 GCC 上使用 -std=c++11 (带有 __debugbreak() 语句的占位符)。我假设 VC++ 在完全支持 C++11 时也可以工作。

【讨论】:

  • 为什么不应该将验证与作业混合使用?我假设验证是为了证明代码处理得当,并且可以轻松删除测试环境而无需更改太多代码。我最担心的是迭代使用只容纳验证宏的多行。例如:bool isOpened = SomeSerializer-&gt;OpenFile("somefile.txt"); assert(isOpened); if(isOpened) {//code using the file} 相比之下,这似乎更优雅:if ( assert(SomeSerializer-&gt;Open("somefile.txt")) ) {//code using the file}
  • 我想我误解了你当时想要做什么。从原始问题中,您使用了 int x = NT_ASSERT(1/y) 的示例。在这种情况下,您实际上要断言什么?对我来说,断言 y 不是 0 是合乎逻辑的。我误解了这个要求吗?如果没有,那么 NT_ASSERT(1/y) 是不够的。您需要类似 int x = NT_ASSERT(1/y, y!=0, "y can't be zero") 之类的东西。换句话说,原始要求似乎缺少断言所依据的谓词。它只是包含您要评估的表达式。
猜你喜欢
  • 2016-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-06
  • 2012-02-16
  • 1970-01-01
相关资源
最近更新 更多