C++11 靜態斷言(static_assert)

 

簡介app

C++0x中引入了static_assert這個關鍵字,用來作編譯期間的斷言,所以叫作靜態斷言。函數

其語法很簡單:static_assert(常量表達式,提示字符串)。post

若是第一個參數常量表達式的值爲真(true或者非零值),那麼static_assert不作任何事情,就像它不存在同樣,不然會產生一條編譯錯誤,錯誤位置就是該static_assert語句所在行,錯誤提示就是第二個參數提示字符串。性能

 

說明編碼

使用static_assert,咱們能夠在編譯期間發現更多的錯誤,用編譯器來強制保證一些契約,並幫助咱們改善編譯信息的可讀性,尤爲是用於模板的時候。spa

static_assert能夠用在全局做用域中,命名空間中,類做用域中,函數做用域中,幾乎能夠不受限制的使用。code

編譯器在遇到一個static_assert語句時,一般馬上將其第一個參數做爲常量表達式進行演算,但若是該常量表達式依賴於某些模板參數,則延遲到模板實例化時再進行演算,這就讓檢查模板參數成爲了可能。ip

因爲以前有望加入C++0x標準的concepts提案最終被否決了,所以對於檢查模板參數是否符合指望的重任,就要靠static_assert來完成了,因此如何構造適當的常量表達式,將是一個值得探討的話題。作用域

性能方面,因爲是static_assert編譯期間斷言,不生成目標代碼,所以static_assert不會形成任何運行期性能損失。字符串

 

範例

下面是一個來自MSDN的簡單範例:

static_assert(sizeof(void *) == 4, "64-bit code generation is not supported.");

static_assert用來確保編譯僅在32位的平臺上進行,不支持64位的平臺,該語句能夠放在文件的開頭處,這樣能夠儘早檢查,以節省失敗狀況下的編譯時間。

再看另外一個範例:

 1: struct MyClass
 2: {
 3: char m_value;
 4: };
 5:  
 6: struct MyEmptyClass
 7: {
 8: void func();
 9: };
 10:  
 11: // 確保MyEmptyClass是一個空類(沒有任何非靜態成員變量,也沒有虛函數)
 12: static_assert(std::is_empty<MyEmptyClass>::value, "empty class needed");
 13:  
 14: //確保MyClass是一個非空類
 15: static_assert(!std::is_empty<MyClass>::value, "non-empty class needed");
 16:  
 17: template <typename T, typename U, typename V>
 18: class MyTemplate
 19: {
 20: // 確保模板參數T是一個非空類
 21: static_assert(
 22: !std::is_empty<T>::value,
 23: "T should be n non-empty class"
 24: );
 25:  
 26: // 確保模板參數U是一個空類
 27: static_assert(
 28: std::is_empty<U>::value,
 29: "U should be an empty class"
 30: );
 31:  
 32: // 確保模板參數V是從std::allocator<T>直接或間接派生而來,
 33: // 或者V就是std::allocator<T>
 34: static_assert(
 35: std::is_base_of<std::allocator<T>, V>::value,
 36: "V should inherit from std::allocator<T>"
 37: );
 38:  
 39: };
 40:  
 41: // 僅當模板實例化時,MyTemplate裏面的那三個static_assert纔會真正被演算,
 42: // 藉此檢查模板參數是否符合指望
 43: template class MyTemplate<MyClass, MyEmptyClass, std::allocator<MyClass>>;
經過這個例子咱們能夠看出來,static_assert能夠很靈活的使用,經過構造適當的常量表達式,咱們能夠檢查不少東西。好比範例中std::is_emptystd::is_base_of都是C++新的標準庫提供的type traits模板,咱們使用這些模板能夠檢查不少類型信息。

 

相關比較

咱們知道,C++現有的標準中,就有assert、#error兩個設施,也是用來檢查錯誤的,還有一些第三方的靜態斷言實現。

assert是運行期斷言,它用來發現運行期間的錯誤,不能提早到編譯期發現錯誤,也不具備強制性,也談不上改善編譯信息的可讀性,既然是運行期檢查,對性能固然是有影響的,因此常常在發行版本中,assert都會被關掉;

#error可看作預編譯期斷言,甚至都算不上斷言,僅僅能在預編譯時顯示一個錯誤信息,它能作的很少,能夠參與預編譯的條件檢查,因爲它沒法得到編譯信息,固然就作不了更進一步分析了。

那麼,在stastic_assert提交到C++0x標準以前,爲了彌補assert#error的不足,出現了一些第三方解決方案,能夠做編譯期的靜態檢查,例如BOOST_STATIC_ASSERTLOKI_STATIC_CHECK,但因爲它們都是利用了一些編譯器的隱晦特性實現的trick,可移植性、簡便性都不是太好,還會下降編譯速度,並且功能也不夠完善,例如BOOST_STATIC_ASSERT就不能定義錯誤提示文字,而LOKI_STATIC_CHECK則要求提示文字知足C++類型定義的語法。

 

譯器實現

gcc和vc的實現沒有太大的差異,均不支持中文提示,很是遺憾,並且VC僅支持ASCII編碼,L前綴就會編譯出錯。GCC好像能夠支持其餘編碼,例如L前綴和U前綴,但我試過發現結果和ASCII編碼同樣。

static_assert的錯誤提示,VC比GCC稍微友好一些,VC對上下文和調用堆棧都有較清晰描述,相比之下,GCC的提示簡陋一些,但也算比較明確吧,原本麼,static_assert很大程度上就是爲了編譯器的提示信息更加友好而存在的。

 

應用研究

最後再舉個例子,用來判斷某個類是否有某個指定名字的成員,供參考和體驗。其實static_assert的應該很大程度上就是看如何構造常量表達式了,這個例子中使用了decltype關鍵字(也是C++0x新特性)和SFINAE技巧,以及一些編譯器相關的技巧(主要是解決VC編譯器的bug),具體的技術細節和原理在從此的文章中還會仔細探討。

 1: // 判斷類是否含有foo這個成員變量和成員函數
 2: // 針對GCC的實現,基本上就是針對標準C++的實現
 3: #ifdef __GNUC__
 4:  
 5: // check_property_foo函數的兩個重載版本,結合decltype,
 6: // 經過SFINAE在編譯期推導指定類型是否含有foo這個成員變量
 7: char check_property_foo(...);
 8:  
 9: template <typename T>
 10: void* check_property_foo(T const& t, decltype(&(t.foo)) p = 0);
 11:  
 12: // 這個類模板經過check_property_foo得出T是否含有foo這個成員變量
 13: template <typename T>
 14: struct has_property_foo : public std::integral_constant<
 15: bool, sizeof(check_property_foo(*static_cast(0))) == sizeof(void*)>
 16: {
 17: };
 18:  
 19: // check_method_foo函數的兩個重載版本,結合decltype,
 20: // 經過SFINAE在編譯期推導指定類型是否含有foo這個成員函數
 21: char check_method_foo(...);
 22:  
 23: template <typename T>
 24: void* check_method_foo(T const& t, decltype(&(T::foo)) p = 0);
 25:  
 26: // 這個類模板經過check_method_foo得出T是否含有foo這個成員函數
 27: template <typename T>
 28: struct has_method_foo : public std::integral_constant<
 29: bool, !has_property_foo::value &&
 30: sizeof(check_method_foo(*static_cast(0))) == sizeof(void*)>
 31: {
 32: };
 33: #endif
 34:  
 35: // 針對VC的實現,因爲VC編譯器在處理decltype和SFINAE狀況下存在bug,
 36: // 咱們只能採用一些花招來繞過這個bug
 37: #ifdef _MSC_VER
 38:  
 39: // check_member_foo函數的兩個重載版本,結合decltype,
 40: // 經過SFINAE在編譯期推導指定類型是否含有foo這個成員變量或函數
 41: char check_member_foo(...);
 42: 
 43: template <typename T>
 44: auto check_member_foo(T const& t, decltype(&(t.foo)) p = 0)->decltype(p);
 45:  
 46: // 這個類模板經過check_member_foo得出T是否含有foo這個成員變量
 47: template <typename T>
 48: struct has_property_foo
 49: {
 50: static const bool value =
 51: sizeof(check_member_foo(*static_cast(0))) != sizeof(char) &&
 52: std::is_pointerstatic_cast(0)))>::value;
 53: };
 54:
55: // 這個類模板經過check_member_foo得出T是否含有foo這個成員函數
 56: template <typename T>
 57: struct has_method_foo
 58: {
 59: static const bool value =
 60: sizeof(check_member_foo(*static_cast(0))) != sizeof(char) &&
 61: !std::is_pointerstatic_cast(0)))>::value;
 62: };
 63:  
 64: #endif
 65:  
 66: // 先定義幾個類供實現檢測
 67: struct WithPropertyFoo
 68: {
 69: int foo;
 70: };
 71:  
 72: struct WithMethodFoo
 73: {
 74: void foo();
 75: };
 76:  
 77: struct WithRefPorpertyFoo
 78: {
 79: int& foo;
 80: };
 81:  
 82: struct WithoutFoo
 83: {
 84: void bar();
 85: };
 86:  
 87: // 用static_assert對這些條件進行檢查
 88: static_assert(has_property_foo::value, "property foo needed");
 89: static_assert(has_method_foo::value, "method foo needed");
 90: static_assert(!has_property_foo::value, "no property foo");
 91: static_assert(!has_method_foo::value, "no methoed foo");
 92: static_assert(has_property_foo::value, "property foo needed");
 93: static_assert(!has_method_foo::value, "no method foo");
相關文章
相關標籤/搜索