Boost原始碼剖析--
Boost原始碼剖析--<boost/assert.hpp>
By 馬冬亮(凝霜 Loki)
一個人的戰爭(http://blog.csdn.net/MDL13412)
標頭檔案:<boost/assert.hpp>
定位:
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: mdl2009@vip.qq.com
// 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一起使用效果更佳。
相關文章
- Java集合原始碼剖析——ArrayList原始碼剖析Java原始碼
- Spring原始碼剖析9:Spring事務原始碼剖析Spring原始碼
- epoll–原始碼剖析原始碼
- Thread原始碼剖析thread原始碼
- Handler原始碼剖析原始碼
- HashMap原始碼剖析HashMap原始碼
- Kafka 原始碼剖析(一)Kafka原始碼
- Flutter 原始碼剖析(一)Flutter原始碼
- 全面剖析 Redux 原始碼Redux原始碼
- 深入剖析LinkedList原始碼原始碼
- Java LinkedList 原始碼剖析Java原始碼
- vue原始碼剖析(一)Vue原始碼
- spark核心原始碼深度剖析Spark原始碼
- STL原始碼剖析——vector容器原始碼
- mmdetection原始碼剖析(1)--NMS原始碼
- Dolphinscheduler DAG核心原始碼剖析原始碼
- 深入剖析(JDK)ArrayQueue原始碼JDK原始碼
- 深入剖析RocketMQ原始碼-NameServerMQ原始碼Server
- jQuery原始碼剖析(五) - 事件繫結原理剖析jQuery原始碼事件
- 我的原始碼閱讀之路:redux原始碼剖析原始碼Redux
- Spring AOP 原理原始碼深度剖析Spring原始碼
- Redux 原始碼剖析及應用Redux原始碼
- YYImage原始碼剖析與學習原始碼
- YYModel 原始碼剖析:關注效能原始碼
- Flutter事件分發原始碼剖析Flutter事件原始碼
- 剖析 React 原始碼:render 流程(一)React原始碼
- Graphx 原始碼剖析-圖的生成原始碼
- Flutter原始碼剖析(一):原始碼獲取與構建Flutter原始碼
- ArrayList原始碼剖析與程式碼實測原始碼
- petite-vue原始碼剖析-為什麼要讀原始碼?Vue原始碼
- Axios原始碼深度剖析 – AJAX新王者iOS原始碼
- Redis原始碼剖析之主從複製Redis原始碼
- Axios原始碼深度剖析 - AJAX新王者iOS原始碼
- Vue原始碼剖析——render、patch、updata、vnodeVue原始碼
- [Android] 狀態機 StateMachine 原始碼剖析AndroidMac原始碼
- Spring原始碼剖析6:Spring AOP概述Spring原始碼
- 原始碼剖析 golang 中 sync.Mutex原始碼GolangMutex
- YYAsyncLayer 原始碼剖析:非同步繪製原始碼非同步
- YYCache 原始碼剖析:一覽亮點原始碼