C++ 如何在不使用abort()的情况下断言()?
如果我使用C++ 如何在不使用abort()的情况下断言()?,c++,exception,assert,C++,Exception,Assert,如果我使用assert()而断言失败,那么assert()将调用abort(),从而突然结束正在运行的程序。在我的产品代码中我负担不起。有没有一种方法可以在运行时断言,但却能够捕获失败的断言,这样我就有机会优雅地处理它们?是的,事实上有。您需要自己编写一个自定义断言函数,因为C++的assert()正是C的assert(),附带了abort()“功能”。幸运的是,这非常简单 Assert.hh main.cc 但它清楚地表明了程序员的意图:做出断言。使用这种方法,断言也更容易获得,就像普通的as
assert()
而断言失败,那么assert()
将调用abort()
,从而突然结束正在运行的程序。在我的产品代码中我负担不起。有没有一种方法可以在运行时断言,但却能够捕获失败的断言,这样我就有机会优雅地处理它们?是的,事实上有。您需要自己编写一个自定义断言函数,因为C++的assert()
正是C的assert()
,附带了abort()
“功能”。幸运的是,这非常简单
Assert.hh
main.cc
但它清楚地表明了程序员的意图:做出断言。使用这种方法,断言也更容易获得,就像普通的assert()
s一样
<> P>为了了解更多的细节,请参阅Bjarne Stroustrup的C++编程语言3E,第24.3.7.2.24/< > P/>在C/C++中的断言只在调试版本中运行。所以这不会在运行时发生。一般来说,断言应该标记发生时表示bug的事情,并且通常在代码中显示假设等 如果您想让代码在运行时(在发行版中)检查错误,您可能应该使用异常,而不是断言,因为这些是它们的设计目的。您的答案基本上用断言语法包装了一个异常抛出器。虽然这会起作用,但我看不出这比一开始就抛出异常有什么特别的好处。采取在断言之后继续的方法。glib是Gnome(通过GTK)使用的底层平台独立性库。下面是一个宏,它检查一个前提条件,如果前提条件失败,它将打印一个堆栈跟踪
#define RETURN_IF_FAIL(expr) do { \
if (!(expr)) \
{ \
fprintf(stderr, \
"file %s: line %d (%s): precondition `%s' failed.", \
__FILE__, \
__LINE__, \
__PRETTY_FUNCTION__, \
#expr); \
print_stack_trace(2); \
return; \
}; } while(0)
#define RETURN_VAL_IF_FAIL(expr, val) do { \
if (!(expr)) \
{ \
fprintf(stderr, \
"file %s: line %d (%s): precondition `%s' failed.", \
__FILE__, \
__LINE__, \
__PRETTY_FUNCTION__, \
#expr); \
print_stack_trace(2); \
return val; \
}; } while(0)
下面是为使用gnu工具链(gcc)的环境编写的打印堆栈跟踪的函数:
以下是如何使用宏:
char *doSomething(char *ptr)
{
RETURN_VAL_IF_FAIL(ptr != NULL, NULL); // same as assert(ptr != NULL), but returns NULL if it fails.
if( ptr != NULL ) // Necessary if you want to define the macro only for debug builds
{
...
}
return ptr;
}
void doSomethingElse(char *ptr)
{
RETURN_IF_FAIL(ptr != NULL);
}
以下是我在“assert.h”(Mac OS 10.4)中的内容:
基于此,将对abort()的调用替换为throw(异常)。您可以将字符串格式化为异常的错误消息,而不是printf。最后,你会得到这样的结果:
#define assert(e) ((void) ((e) ? 0 : my_assert (#e, __FILE__, __LINE__)))
#define my_assert( e, file, line ) ( throw std::runtime_error(\
std::string(file:)+boost::lexical_cast<std::string>(line)+": failed assertion "+e))
并在可以自由包含所需的所有标题的地方实现它
如果您需要,可以将其包装在一些#ifdef DEBUG中,如果您总是想运行这些检查,则不需要
_set_error_mode(_OUT_TO_MSGBOX);
相信我,此函数可以帮助您。如果您想抛出包含断言信息的字符串:
由于您只能获得默认构造的异常,因此没有太多好处,因为您无法将任何运行时数据传输到catch站点……模板化assert参数而不是使其为int有什么附加值?无论如何,它最终都必须转换为int。@Greg Rogers Bjarne Stroustrup为您提到的问题提供了一个解决方案:您可以添加一个自定义对象,作为Assert()的额外参数抛出。缺点是由于额外的参数,Assert()看起来不再像Assert()。@JohnMcG:template参数给了我额外的灵活性。您可以将任何内容传递给哪个运算符的Assert()函数!()被定义并返回可转换为布尔的内容。Boolean函数、ints函数和一些自定义对象也是如此。它非常简单,代码表明这只是一个包装异常。但我希望人们能超越表面现象。类似的咆哮可以说是旧的assert(),但它提供了一个非常有用的任务。这个技巧只是试图解决旧assert()的一个缺点,而不需要额外的成本。在生产版本中运行或不运行assert是一个编译器选项。不管优化选项如何,默认情况下它们都会在gcc中运行。我个人认为将断言保持在发布模式是个好主意。当你的客户端使用应用程序时,你可能更喜欢随机崩溃,尤其是如果你的程序在断言变为false时会导致未定义的行为;它将允许客户端至少给出一个更有价值的错误消息和更容易的复制步骤;考虑到这本来就不应该发生;而你认为(并保证)不会。不幸的是,这里的措辞含糊不清<只有在定义了
NDEBUG
的情况下,code>assert()才会变成no-op,我认为没有任何编译器会根据给定的优化和/或调试选项自动定义它。也许IDE会这样做,这就混淆了问题。不管怎样,争论就变成了是否在已发布的版本中定义NDEBUG
,是否与任何其他#ifdef
d代码冲突,是否需要编写自己的宏以获得更精确的行为,等等。因此,宏是否优于模板函数还有待讨论。此外,序列运算符意味着操作数可以按任意顺序执行。我怀疑这是您在_asert()中的意思。第一个块中的“_assert”函数来自我的系统头(0s10.4),我希望他们知道自己在做什么。宏使获取文件和行信息变得更容易。但从某种意义上说,它变得相当漂亮…@wilhelmtell我相信这是迄今为止最好的解决方案,因为我不同意使用exception。异常应该是异常的,但处理断言不是。另外,如果您计划在发布版本中保留断言,我认为您应该这样做,除非性能至关重要,否则这种方法将更有效。你也可以对它进行个性化设置,这样当它发生时,你的程序就不必退出,你就可以得到信息。我建议阅读实用程序员书中关于断言和例外的章节。另外,这似乎是一个只在Windows上运行的函数:至少你应该解释一下使用它所需要的头/编译器/任何东西。
#define RETURN_IF_FAIL(expr) do { \
if (!(expr)) \
{ \
fprintf(stderr, \
"file %s: line %d (%s): precondition `%s' failed.", \
__FILE__, \
__LINE__, \
__PRETTY_FUNCTION__, \
#expr); \
print_stack_trace(2); \
return; \
}; } while(0)
#define RETURN_VAL_IF_FAIL(expr, val) do { \
if (!(expr)) \
{ \
fprintf(stderr, \
"file %s: line %d (%s): precondition `%s' failed.", \
__FILE__, \
__LINE__, \
__PRETTY_FUNCTION__, \
#expr); \
print_stack_trace(2); \
return val; \
}; } while(0)
void print_stack_trace(int fd)
{
void *array[256];
size_t size;
size = backtrace (array, 256);
backtrace_symbols_fd(array, size, fd);
}
char *doSomething(char *ptr)
{
RETURN_VAL_IF_FAIL(ptr != NULL, NULL); // same as assert(ptr != NULL), but returns NULL if it fails.
if( ptr != NULL ) // Necessary if you want to define the macro only for debug builds
{
...
}
return ptr;
}
void doSomethingElse(char *ptr)
{
RETURN_IF_FAIL(ptr != NULL);
}
#define assert(e) ((void) ((e) ? 0 : __assert (#e, __FILE__, __LINE__)))
#define __assert(e, file, line) ((void)printf ("%s:%u: failed assertion `%s'\n", file, line, e), abort(), 0)
#define assert(e) ((void) ((e) ? 0 : my_assert (#e, __FILE__, __LINE__)))
#define my_assert( e, file, line ) ( throw std::runtime_error(\
std::string(file:)+boost::lexical_cast<std::string>(line)+": failed assertion "+e))
void my_assert( const char* e, const char* file, int line);
_set_error_mode(_OUT_TO_MSGBOX);