Boost原始碼剖析--

凝霜發表於2012-05-05

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一起使用效果更佳。

相關文章