Boost源码剖析--<boost/assert.hpp>
By 马冬亮(凝霜 Loki)
一个人的战争(http://blog.csdn.net/MDL13412)
头文件:定位:
BOOST_ASSERT类似于标准库中的assert(定义在<cassert>),目的是在Boost库和用户代码中都可以使用。
分析:
默认情况下BOOST_ASSERT(expr)等价于assert(expr),但是如果在#include <boost/assert.hpp>前定义BOOST_DISABLE_ASSERTS,那么BOOST_ASSERT(expr)就被实现为((void)0),即不做任何事情,编译器可以很容易的将其优化掉。这样做的优势在于,用户编写代码的时候,可以在不影响assert的前提下,开启和禁用一些断言,为代码提供了更多灵活性。
如果定义了BOOST_ENABLE_ASSERT_HANDLER,Boost库为用户提供了一个用于断言出错时的回调函数assertion_failed,需要用户自己去实现。
BOOST_ASSERT_MSG可以在出错的时候附加一段用户自定义的错误描述信息,帮助用户更好的理解及处理错误。
如果不定义BOOST_ENABLE_ASSERT_HANDLER,那么BOOST_ASSERT_MSG会将断言相关的信息转发到BOOST_ASSERT_MSG_OSTREAM,默认是std::cerr,并且接下来调用std::abort()。如果需要自定义输出流,用户需要在包含<boost/assert.hpp>前指定BOOST_ASSERT_MSG_OSTREAM的值。
BOOST_VERIFY的功能和BOOST_ASSERT基本一致,所不同的是BOOST_VERIFY的表达式总是被求值,即通过NDEBUG和BOOST_DISABLE_ASSERTS禁用断言时,表达式依然会被求值,但是结果会被丢弃。
注意:
<boost/assert.hpp>没有头文件guard,这是为了保证多次包含此文件时,通过预先定义不同的宏,实现不同的断言。
源码剖析:
// // boost/assert.hpp - BOOST_ASSERT(expr) // BOOST_ASSERT_MSG(expr, msg) // BOOST_VERIFY(expr) // // Copyright (c) 2001, 2002 Peter Dimov and Multi Media Ltd. // Copyright (c) 2007 Peter Dimov // Copyright (c) Beman Dawes 2011 // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // // Note: There are no include guards. This is intentional. // // See http://www.boost.org/libs/utility/assert.html for documentation. // // // Stop inspect complaining about use of 'assert': // // boostinspect:naassert_macro // // Comment By: 凝霜 // E-mail: [email protected] // Blog: http://blog.csdn.net/mdl13412 //--------------------------------------------------------------------------------------// // BOOST_ASSERT // //--------------------------------------------------------------------------------------// // 由于没有头文件guard,每次包含文件时都要undef掉宏定义,这样可以根据预定义的宏,实现不同版本的断言。 #undef BOOST_ASSERT // 定义BOOST_DISABLE_ASSERTS,关闭断言,不会影响到标准库的assert #if defined(BOOST_DISABLE_ASSERTS) // 编译器面对这样的一个定义,一定会优化掉,如果你的编译器不能优化。。。相信它也不能正确编译boost库^_^ # define BOOST_ASSERT(expr) ((void)0) // 预定义BOOST_ENABLE_ASSERT_HANDLER的同时,需要用户自己实现assertion_failed,完成错误断言的回调。 #elif defined(BOOST_ENABLE_ASSERT_HANDLER) #include <boost/current_function.hpp> namespace boost { void assertion_failed(char const * expr, char const * function, char const * file, long line); // user defined } // namespace boost // 经典的assert实现,很简单,正确时不做任何事情,错误时调用用户自定义的回调函数,进行错误处理。 // BOOST_CURRENT_FUNCTION用于获取当前函数名称,需要编译器的支持,在<boost/current_function.hpp>中有实现。 #define BOOST_ASSERT(expr) ((expr) \ ? ((void)0) \ : ::boost::assertion_failed(#expr, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__)) // 默认情况下,使用的是标准库中的assert,可以通过NDEBUG和BOOST_DISABLE_ASSERTS进行禁用。 #else # include <assert.h> // .h to support old libraries w/o <cassert> - effect is the same # define BOOST_ASSERT(expr) assert(expr) #endif //--------------------------------------------------------------------------------------// // BOOST_ASSERT_MSG // //--------------------------------------------------------------------------------------// // 和 BOOST_ASSERT相比, BOOST_ASSERT_MSG可以附加一段用户自定义的消息, // 这有助于描述断言信息,更快、更好的处理断言,定位错误。 # undef BOOST_ASSERT_MSG #if defined(BOOST_DISABLE_ASSERTS) || defined(NDEBUG) #define BOOST_ASSERT_MSG(expr, msg) ((void)0) #elif defined(BOOST_ENABLE_ASSERT_HANDLER) #include <boost/current_function.hpp> namespace boost { void assertion_failed_msg(char const * expr, char const * msg, char const * function, char const * file, long line); // user defined } // namespace boost #define BOOST_ASSERT_MSG(expr, msg) ((expr) \ ? ((void)0) \ : ::boost::assertion_failed_msg(#expr, msg, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__)) #else // 注意:这里需要头文件guard,因为默认的行为只能被定义一次,否则会出现重复定义的问题。 #ifndef BOOST_ASSERT_HPP #define BOOST_ASSERT_HPP #include <cstdlib> #include <iostream> #include <boost/current_function.hpp> // IDE's like Visual Studio perform better if output goes to std::cout or // some other stream, so allow user to configure output stream: // 用户可以通过预定义,将错误信息输出到不同的流,这个流甚至可以是socket,只要你实现了ostream接口的封装。 #ifndef BOOST_ASSERT_MSG_OSTREAM # define BOOST_ASSERT_MSG_OSTREAM std::cerr #endif namespace boost { namespace assertion { namespace detail { // 打印断言错误信息,并且crash掉程序。 // 发生异常的时候,最好的办法就是crash掉程序,如果忽略,会导致更多的错误。 inline void assertion_failed_msg(char const * expr, char const * msg, char const * function, char const * file, long line) { BOOST_ASSERT_MSG_OSTREAM << "***** Internal Program Error - assertion (" << expr << ") failed in " << function << ":\n" << file << '(' << line << "): " << msg << std::endl; std::abort(); } } // detail } // assertion } // detail #endif #define BOOST_ASSERT_MSG(expr, msg) ((expr) \ ? ((void)0) \ : ::boost::assertion::detail::assertion_failed_msg(#expr, msg, \ BOOST_CURRENT_FUNCTION, __FILE__, __LINE__)) #endif //--------------------------------------------------------------------------------------// // BOOST_VERIFY // //--------------------------------------------------------------------------------------// #undef BOOST_VERIFY #if defined(BOOST_DISABLE_ASSERTS) || ( !defined(BOOST_ENABLE_ASSERT_HANDLER) && defined(NDEBUG) ) // 在任何情况下,expr一定会被求值。 # define BOOST_VERIFY(expr) ((void)(expr)) #else # define BOOST_VERIFY(expr) BOOST_ASSERT(expr) #endif实例:
我们在类里面定义一个断言,并让其为假。
#include <iostream> #include <cstdlib> #include <boost/assert.hpp> using namespace std; class Dummy { public: void foo() { const bool expr = false; BOOST_ASSERT(expr); } }; int main(int argc, char** argv) { Dummy dummy; dummy.foo(); return 0; }
boostsource: main.cpp:15: void Dummy::foo(): Assertion `expr' failed. 运行 失败 (退出值 1, 总计时间: 238毫秒)我们看到,在断言失败后,错误被定位在main.cpp的第15行,其属于Dummy类的foo()函数。
接下来我们尝试使用BOOST_ASSERT_MSG
#include <iostream> #include <cstdlib> #include <boost/assert.hpp> using namespace std; class Dummy { public: void foo() { const bool expr = false; BOOST_ASSERT_MSG(expr, "Oops, XXX is invalid"); } }; int main(int argc, char** argv) { Dummy dummy; dummy.foo(); return 0; }
***** Internal Program Error - assertion (expr) failed in void Dummy::foo(): main.cpp(15): Oops, XXX is invalid 运行 失败 (退出值 1, 总计时间: 355毫秒)这次我们看到断言为假时,系统将我们定义的错误信息显示了出来。如果我们在断言的自定义消息部分给出了有意义的信息,那么在断言为假的时候,我们可以快速找出程序的Bug,显著提升调试的效率。
#include <iostream> #include <cstdlib> #define BOOST_ENABLE_ASSERT_HANDLER #include <boost/assert.hpp> using namespace std; namespace boost { void assertion_failed_msg(char const * expr, char const * msg, char const * function, char const * file, long line) { std::cout << "Something to handle assert" << std::endl; } } class Dummy { public: void foo() { const bool expr = false; BOOST_ASSERT_MSG(expr, "Oops, XXX is invalid"); } }; int main(int argc, char** argv) { Dummy dummy; dummy.foo(); return 0; }
Something to handle assert 运行 失败 (退出值 1, 总计时间: 343毫秒)使用用户自定义的回调函数,我们拥有非常大的自由度,例如可以将错误信息写到日志,或者使用GUI将错误显示出来等等。
注意:用户自定义的回调函数只能定义一次!
最后再给出一个同时使用assert和BOOST_ASSERT_MSG的例子,这里你将看到我是如何通过HACK来让assert输出用户自定义的信息的:
#include <iostream> #include <cstdlib> #include <cassert> // 禁用BOOST_ASSERT,不会影响std::assert #define BOOST_DISABLE_ASSERTS #include <boost/assert.hpp> using namespace std; class Dummy { public: void foo() { const bool expr = false; BOOST_ASSERT_MSG(expr, "Oops, XXX is invalid"); assert(expr && "Haha... This is a hack"); } }; int main(int argc, char** argv) { Dummy dummy; dummy.foo(); return 0; }
boostsource: main.cpp:19: void Dummy::foo(): Assertion `expr && "Haha... This is a hack"' failed. 运行 失败 (退出值 1, 总计时间: 224毫秒)本例中,我将BOOST_ASSERT_MSG禁用,通过结果我们可以看出,这并没有影响到std::assert的功能。
另外输出中的
Assertion `expr && "Haha... This is a hack"是我的HACK过程,可以模拟BOOST_ASSERT_MSG自定义消息的部分,在不是用Boost的时候,这个技巧非常有用。
总结:
Boost库的断言为我们提供了非常大的灵活性,与std::assert一起使用效果更佳。