簡(jiǎn)介 C++0x中引入了static_assert這個(gè)關(guān)鍵字,,用來(lái)做編譯期間的斷言,,因此叫做靜態(tài)斷言。 其語(yǔ)法很簡(jiǎn)單:static_assert(常量表達(dá)式,,提示字符串),。 如果第一個(gè)參數(shù)常量表達(dá)式的值為真(true或者非零值),那么static_assert不做任何事情,,就像它不存在一樣,,否則會(huì)產(chǎn)生一條編譯錯(cuò)誤,錯(cuò)誤位置就是該static_assert語(yǔ)句所在行,,錯(cuò)誤提示就是第二個(gè)參數(shù)提示字符串,。
說(shuō)明 使用static_assert,,我們可以在編譯期間發(fā)現(xiàn)更多的錯(cuò)誤,用編譯器來(lái)強(qiáng)制保證一些契約,,并幫助我們改善編譯信息的可讀性,,尤其是用于模板的時(shí)候。 static_assert可以用在全局作用域中,,命名空間中,,類作用域中,函數(shù)作用域中,,幾乎可以不受限制的使用,。 編譯器在遇到一個(gè)static_assert語(yǔ)句時(shí),通常立刻將其第一個(gè)參數(shù)作為常量表達(dá)式進(jìn)行演算,,但如果該常量表達(dá)式依賴于某些模板參數(shù),則延遲到模板實(shí)例化時(shí)再進(jìn)行演算,,這就讓檢查模板參數(shù)成為了可能,。 由于之前有望加入C++0x標(biāo)準(zhǔn)的concepts提案最終被否決了,因此對(duì)于檢查模板參數(shù)是否符合期望的重任,,就要靠static_assert來(lái)完成了,,所以如何構(gòu)造適當(dāng)?shù)某A勘磉_(dá)式,將是一個(gè)值得探討的話題,。 性能方面,,由于是static_assert編譯期間斷言,不生成目標(biāo)代碼,,因此static_assert不會(huì)造成任何運(yùn)行期性能損失,。
范例 下面是一個(gè)來(lái)自MSDN的簡(jiǎn)單范例: static_assert(sizeof(void *) == 4, "64-bit code generation is not supported."); 該static_assert用來(lái)確保編譯僅在32位的平臺(tái)上進(jìn)行,不支持64位的平臺(tái),,該語(yǔ)句可以放在文件的開(kāi)頭處,,這樣可以盡早檢查,以節(jié)省失敗情況下的編譯時(shí)間,。 再看另一個(gè)范例: 1: struct MyClass 2: { 3: char m_value; 4: }; 5: 6: struct MyEmptyClass 7: { 8: void func(); 9: }; 10: 11: // 確保MyEmptyClass是一個(gè)空類(沒(méi)有任何非靜態(tài)成員變量,,也沒(méi)有虛函數(shù)) 12: static_assert(std::is_empty<MyEmptyClass>::value, "empty class needed"); 13: 14: //確保MyClass是一個(gè)非空類 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: // 確保模板參數(shù)T是一個(gè)非空類 21: static_assert( 22: !std::is_empty<T>::value, 23: "T should be n non-empty class" 24: ); 25: 26: // 確保模板參數(shù)U是一個(gè)空類 27: static_assert( 28: std::is_empty<U>::value, 29: "U should be an empty class" 30: ); 31: 32: // 確保模板參數(shù)V是從std::allocator<T>直接或間接派生而來(lái), 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: // 僅當(dāng)模板實(shí)例化時(shí),,MyTemplate里面的那三個(gè)static_assert才會(huì)真正被演算,, 42: // 藉此檢查模板參數(shù)是否符合期望 43: template class MyTemplate<MyClass, MyEmptyClass, std::allocator<MyClass>>; 通過(guò)這個(gè)例子我們可以看出來(lái),static_assert可以很靈活的使用,,通過(guò)構(gòu)造適當(dāng)?shù)某A勘磉_(dá)式,,我們可以檢查很多東西。比如范例中std::is_empty和std::is_base_of都是C++新的標(biāo)準(zhǔn)庫(kù)提供的type traits模板,我們使用這些模板可以檢查很多類型信息,。
相關(guān)比較 我們知道,,C++現(xiàn)有的標(biāo)準(zhǔn)中,,就有assert,、#error兩個(gè)設(shè)施,,也是用來(lái)檢查錯(cuò)誤的,,還有一些第三方的靜態(tài)斷言實(shí)現(xiàn)。 assert是運(yùn)行期斷言,,它用來(lái)發(fā)現(xiàn)運(yùn)行期間的錯(cuò)誤,,不能提前到編譯期發(fā)現(xiàn)錯(cuò)誤,,也不具有強(qiáng)制性,也談不上改善編譯信息的可讀性,,既然是運(yùn)行期檢查,,對(duì)性能當(dāng)然是有影響的,所以經(jīng)常在發(fā)行版本中,,assert都會(huì)被關(guān)掉,; #error可看做預(yù)編譯期斷言,甚至都算不上斷言,,僅僅能在預(yù)編譯時(shí)顯示一個(gè)錯(cuò)誤信息,,它能做的不多,可以參與預(yù)編譯的條件檢查,,由于它無(wú)法獲得編譯信息,,當(dāng)然就做不了更進(jìn)一步分析了。 那么,,在stastic_assert提交到C++0x標(biāo)準(zhǔn)之前,,為了彌補(bǔ)assert和#error的不足,出現(xiàn)了一些第三方解決方案,,可以作編譯期的靜態(tài)檢查,,例如BOOST_STATIC_ASSERT和LOKI_STATIC_CHECK,但由于它們都是利用了一些編譯器的隱晦特性實(shí)現(xiàn)的trick,,可移植性,、簡(jiǎn)便性都不是太好,還會(huì)降低編譯速度,,而且功能也不夠完善,,例如BOOST_STATIC_ASSERT就不能定義錯(cuò)誤提示文字,而LOKI_STATIC_CHECK則要求提示文字滿足C++類型定義的語(yǔ)法,。
編譯器實(shí)現(xiàn) gcc和vc的實(shí)現(xiàn)沒(méi)有太大的差別,,均不支持中文提示,非常遺憾,而且VC僅支持ASCII編碼,,L前綴就會(huì)編譯出錯(cuò),。GCC好像可以支持其他編碼,例如L前綴和U前綴,,但我試過(guò)發(fā)現(xiàn)結(jié)果和ASCII編碼一樣,。 static_assert的錯(cuò)誤提示,VC比GCC稍微友好一些,,VC對(duì)上下文和調(diào)用堆棧都有較清晰描述,,相比之下,GCC的提示簡(jiǎn)陋一些,,但也算比較明確吧,,本來(lái)么,static_assert很大程度上就是為了編譯器的提示信息更加友好而存在的,。
應(yīng)用研究 最后再舉個(gè)例子,,用來(lái)判斷某個(gè)類是否有某個(gè)指定名字的成員,供參考和體驗(yàn),。其實(shí)static_assert的應(yīng)該很大程度上就是看如何構(gòu)造常量表達(dá)式了,,這個(gè)例子中使用了decltype關(guān)鍵字(也是C++0x新特性)和SFINAE技巧,以及一些編譯器相關(guān)的技巧(主要是解決VC編譯器的bug),,具體的技術(shù)細(xì)節(jié)和原理在今后的文章中還會(huì)仔細(xì)探討,。 1: // 判斷類是否含有foo這個(gè)成員變量和成員函數(shù) 2: // 針對(duì)GCC的實(shí)現(xiàn),基本上就是針對(duì)標(biāo)準(zhǔn)C++的實(shí)現(xiàn) 3: #ifdef __GNUC__ 4: 5: // check_property_foo函數(shù)的兩個(gè)重載版本,,結(jié)合decltype,, 6: // 通過(guò)SFINAE在編譯期推導(dǎo)指定類型是否含有foo這個(gè)成員變量 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: // 這個(gè)類模板通過(guò)check_property_foo得出T是否含有foo這個(gè)成員變量 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函數(shù)的兩個(gè)重載版本,結(jié)合decltype,, 20: // 通過(guò)SFINAE在編譯期推導(dǎo)指定類型是否含有foo這個(gè)成員函數(shù) 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: // 這個(gè)類模板通過(guò)check_method_foo得出T是否含有foo這個(gè)成員函數(shù) 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: // 針對(duì)VC的實(shí)現(xiàn),,由于VC編譯器在處理decltype和SFINAE情況下存在bug, 36: // 我們只能采用一些花招來(lái)繞過(guò)這個(gè)bug 37: #ifdef _MSC_VER 38: 39: // check_member_foo函數(shù)的兩個(gè)重載版本,,結(jié)合decltype,, 40: // 通過(guò)SFINAE在編譯期推導(dǎo)指定類型是否含有foo這個(gè)成員變量或函數(shù) 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: // 這個(gè)類模板通過(guò)check_member_foo得出T是否含有foo這個(gè)成員變量 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: 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: // 先定義幾個(gè)類供實(shí)現(xiàn)檢測(cè) 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對(duì)這些條件進(jìn)行檢查 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");
|
|
來(lái)自: xiejblan > 《專業(yè)知識(shí)》