【问题标题】:Assert with dynamic message?用动态消息断言?
【发布时间】:2025-12-11 03:30:01
【问题描述】:

在我的程序中,我想使用显示错误消息的断言。除了众所周知的 C 和 C++ 解决方法之外,还有一个“真正的”解决方案,因为 BOOST 提供了BOOST_ASSERT_MSG( expr, msg )(另请参阅assert() with message

但是静态消息对我来说还不够,我有时还想显示失败的变量,例如在这样的情况下

BOOST_ASSERT_MSG( length >= 0, "No positive length found! It is " << length )

如您所见,我想将消息“字符串”格式化为stringstreamostream,因为这样我可以轻松地显示自定义类型(假设我已经定义了相关的格式化函数)。

这里的问题是BOOST_ASSERT_MSG 默认需要char const *,所以这是不兼容的。

有没有办法重新定义/重载assertion_failed_msg(),使得使用流作为消息可以工作?怎么样?
(我的幼稚方法失败了,因为编译器首先想对消息本身进行operator&lt;&lt;("foo",bar)...)

【问题讨论】:

    标签: c++ boost assert


    【解决方案1】:

    如果您仅在 Windows 上工作,您可以查看 assert 宏。在引擎盖下它使用_wassert。您可以使用它编写自己的断言宏。例如,在我的情况下,如果我得到一些观点,我想在没有条件的情况下显示断言:

    #ifdef DEBUG
        const std::wstring assert_msg = /* build the string here */;
        _wassert(assert_msg.c_str(), _CRT_WIDE(__FILE__), (unsigned)(__LINE__));
    #endif
    

    我认为在其他操作系统上您也可以使用相同的技巧,只需查看 assert 宏即可。

    【讨论】:

      【解决方案2】:

      这是一个不依赖宏的解决方案。相反,它使用了一点点模板和 lambda 语法。

      template<typename Fn> 
      void assert_fn( bool expr, Fn fn) {
        if (!expr) {
          fn();
          abort();
        }
      }
      

      参数fn 可以是任何可调用的。
      例如,您可以这样称呼它:

      assert_fn( a==b, [&](){ cout << "Assertion failed: a="<< a << 
                                      " is different from but b=" << b << endl; } ); 
      

      优点是输出是您没有显式调用 abort 并且输出是完全可定制的。这个优势当然是 lambda 函数样板文件的七个额外字符:[&amp;](){})

      【讨论】:

        【解决方案3】:

        我使用 BOOST_ASSERT_MSG 和我自己的包装器,因此用多个 operator&lt;&lt; 指定断言消息似乎不那么复杂。

        #if defined ASSERT_ENABLED 
        
            #define ASSERT(cond, msg) {\
                if(!(cond))\
                {\
                    std::stringstream str;\
                    str << msg;\
                    BOOST_ASSERT_MSG(cond, str.str().c_str());\
                }\
            }
        #else
            #define ASSERT(...) 
        #endif
        

        用法示例,提供自定义消息,就像您输出到cout

          ASSERT(execSize == (_oldSize - remaining), "execSize : " << execSize << ", _oldSize : " << _oldSize << ", remaining : " << remaining);
        

        它的作用是,如果定义了ASSERT_ENABLED,则启用断言消息。 if(!(cond)) 部分是优化,如果condtrue,则避免了宏参数msg 指定的代价高昂的字符串操作

        【讨论】:

        • 你真的需要#if defined ASSERT_ENABLED 吗?我认为如果禁用了断言,那么如果使用优化编译器,则无论如何都会删除这些语句。我说的对吗?
        • @SohailSi:是的,#if defined ASSERT_ENABLED 用于优化。通常发布版本意味着禁用断言。这会从二进制文件中删除代码。更小的二进制文件,更少的代码,更好地利用指令缓存。虽然断言节省了大量的调试时间。有时,一个简单的断言检查可以节省 3 天的调试时间。
        • 我的意思是如果定义的 ASSERT_ENABLED 为假,那么编译器应该自动删除 BOOST_ASSERT_MSG 语句,因此应该删除其余的断言代码。因此,无需显式检查 ASSERT_ENABLED。但我不确定为什么需要明确地说出来。我哪里错了?
        【解决方案4】:

        您可以定义自己的宏

        #define ASSERT_WITH_MSG(cond, msg) do \
        { if (!(cond)) { std::ostringstream str; str << msg; std::cerr << str.str(); std::abort(); } \
        } while(0)
        

        【讨论】:

        【解决方案5】:

        实现这一点相对来说微不足道。

        BOOST_ASSERT_MSG( length >= 0, (std::stringstream() << "No positive length found! It is " << length).str().c_str() )
        

        【讨论】:

        • 我希望这样做,但编译器抱怨:error: ‘struct std::basic_ostream&lt;char&gt;’ has no member named ‘str’
        • 哦,是的。我一直忘记 stringstream 库有多坏。
        • 你必须static_cast&lt;std::stringstream&amp;&gt;()operator&lt;&lt;()的返回值才能使用std::stringstream::str()。否则你会尝试调用不存在的std::ostream::str()