Chinaunix首页 | 论坛 | 博客
  • 博客访问: 35022
  • 博文数量: 9
  • 博客积分: 156
  • 博客等级: 入伍新兵
  • 技术积分: 60
  • 用 户 组: 普通用户
  • 注册时间: 2010-08-22 16:36
文章分类
文章存档

2014年(1)

2012年(3)

2011年(5)

我的朋友
最近访客

分类: C/C++

2011-10-21 22:55:49

转载请注明来源:http://www.cppblog.com/thesys/articles/116985.html

简介

C++0x中引入了static_assert这个关键字,用来做编译期间的断言,因此叫做静态断言。

其语法很简单:static_assert(常量表达式,提示字符串)。

如果第一个参数常量表达式的值为真(true或者非零值),那么static_assert不做任何事情,就像它不存在一样,否则会产生一条编译错误,错误位置就是该static_assert语句所在行,错误提示就是第二个参数提示字符串。

 

说明

使用static_assert,我们可以在编译期间发现更多的错误,用编译器来强制保证一些契约,并帮助我们改善编译信息的可读性,尤其是用于模板的时候。

static_assert可以用在全局作用域中,命名空间中,类作用域中,函数作用域中,几乎可以不受限制的使用。

编译器在遇到一个static_assert语句时,通常立刻将其第一个参数作为常量表达式进行演算,但如果该常量表达式依赖于某些模板参数,则延迟到模板实例化时再进行演算,这就让检查模板参数成为了可能。

由于之前有望加入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. 1: struct MyClass

  2.    2: {

  3.    3: char m_value;

  4.    4: };

  5.    5:

  6.    6: struct MyEmptyClass

  7.    7: {

  8.    8: void func();

  9.    9: };

  10.   10:

  11.   11: // 确保MyEmptyClass是一个空类(没有任何非静态成员变量,也没有虚函数)

  12.   12: static_assert(std::is_empty<MyEmptyClass>::value, "empty class needed");

  13.   13:

  14.   14: //确保MyClass是一个非空类

  15.   15: static_assert(!std::is_empty<MyClass>::value, "non-empty class needed");

  16.   16:

  17.   17: template <typename T, typename U, typename V>

  18.   18: class MyTemplate

  19.   19: {

  20.   20: // 确保模板参数T是一个非空类

  21.   21: static_assert(

  22.   22: !std::is_empty<T>::value,

  23.   23: "T should be n non-empty class"

  24.   24: );

  25.   25:

  26.   26: // 确保模板参数U是一个空类

  27.   27: static_assert(

  28.   28: std::is_empty<U>::value,

  29.   29: "U should be an empty class"

  30.   30: );

  31.   31:

  32.   32: // 确保模板参数V是从std::allocator<T>直接或间接派生而来,

  33.   33: // 或者V就是std::allocator<T>

  34.   34: static_assert(

  35.   35: std::is_base_of<std::allocator<T>, V>::value,

  36.   36: "V should inherit from std::allocator"

  37.   37: );

  38.   38:

  39.   39: };

  40.   40:

  41.   41: // 仅当模板实例化时,MyTemplate里面的那三个static_assert才会真正被演算,

  42.   42: // 藉此检查模板参数是否符合期望

  43.   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. 1: // 判断类是否含有foo这个成员变量和成员函数

  2.    2: // 针对GCC的实现,基本上就是针对标准C++的实现

  3.    3: #ifdef __GNUC__

  4.    4:

  5.    5: // check_property_foo函数的两个重载版本,结合decltype,

  6.    6: // 通过SFINAE在编译期推导指定类型是否含有foo这个成员变量

  7.    7: char check_property_foo(...);

  8.    8:

  9.    9: template <typename T>

  10.   10: void* check_property_foo(T const& t, decltype(&(t.foo)) p = 0);

  11.   11:

  12.   12: // 这个类模板通过check_property_foo得出T是否含有foo这个成员变量

  13.   13: template <typename T>

  14.   14: struct has_property_foo : public std::integral_constant<

  15.   15: bool, sizeof(check_property_foo(*static_cast(0))) == sizeof(void*)>

  16.   16: {

  17.   17: };

  18.   18:

  19.   19: // check_method_foo函数的两个重载版本,结合decltype,

  20.   20: // 通过SFINAE在编译期推导指定类型是否含有foo这个成员函数

  21.   21: char check_method_foo(...);

  22.   22:

  23.   23: template <typename T>

  24.   24: void* check_method_foo(T const& t, decltype(&(T::foo)) p = 0);

  25.   25:

  26.   26: // 这个类模板通过check_method_foo得出T是否含有foo这个成员函数

  27.   27: template <typename T>

  28.   28: struct has_method_foo : public std::integral_constant<

  29.   29: bool, !has_property_foo::value &&

  30.   30: sizeof(check_method_foo(*static_cast(0))) == sizeof(void*)>

  31.   31: {

  32.   32: };

  33.   33: #endif

  34.   34:

  35.   35: // 针对VC的实现,由于VC编译器在处理decltype和SFINAE情况下存在bug,

  36.   36: // 我们只能采用一些花招来绕过这个bug

  37.   37: #ifdef _MSC_VER

  38.   38:

  39.   39: // check_member_foo函数的两个重载版本,结合decltype,

  40.   40: // 通过SFINAE在编译期推导指定类型是否含有foo这个成员变量或函数

  41.   41: char check_member_foo(...);

  42.   42:

  43.   43: template <typename T>

  44.   44: auto check_member_foo(T const& t, decltype(&(t.foo)) p = 0)->decltype(p);

  45.   45:

  46.   46: // 这个类模板通过check_member_foo得出T是否含有foo这个成员变量

  47.   47: template <typename T>

  48.   48: struct has_property_foo

  49.   49: {

  50.   50: static const bool value =

  51.   51: sizeof(check_member_foo(*static_cast(0))) != sizeof(char) &&

  52.   52: std::is_pointerstatic_cast(0)))>::value;

  53.   53: };

  54.   54:
  55.   55: // 这个类模板通过check_member_foo得出T是否含有foo这个成员函数

  56.   56: template <typename T>

  57.   57: struct has_method_foo

  58.   58: {

  59.   59: static const bool value =

  60.   60: sizeof(check_member_foo(*static_cast(0))) != sizeof(char) &&

  61.   61: !std::is_pointerstatic_cast(0)))>::value;

  62.   62: };

  63.   63:

  64.   64: #endif

  65.   65:

  66.   66: // 先定义几个类供实现检测

  67.   67: struct WithPropertyFoo

  68.   68: {

  69.   69: int foo;

  70.   70: };

  71.   71:

  72.   72: struct WithMethodFoo

  73.   73: {

  74.   74: void foo();

  75.   75: };

  76.   76:

  77.   77: struct WithRefPorpertyFoo

  78.   78: {

  79.   79: int& foo;

  80.   80: };

  81.   81:

  82.   82: struct WithoutFoo

  83.   83: {

  84.   84: void bar();

  85.   85: };

  86.   86:

  87.   87: // 用static_assert对这些条件进行检查

  88.   88: static_assert(has_property_foo::value, "property foo needed");

  89.   89: static_assert(has_method_foo::value, "method foo needed");

  90.   90: static_assert(!has_property_foo::value, "no property foo");

  91.   91: static_assert(!has_method_foo::value, "no methoed foo");

  92.   92: static_assert(has_property_foo::value, "property foo needed");

  93.   93: static_assert(!has_method_foo::value, "no method foo");
阅读(1618) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~