设为首页 加入收藏

TOP

14.3 dimensionof()
2013-10-07 15:06:29 来源: 作者: 【 】 浏览:66
Tags:14.3 dimensionof

14.3  dimensionof()

14.1节给出的dimensionof()的定义(以NUM_ELEMENTS()的形式)依赖于预处理器进行的文本替换,因而存在一个严重的缺陷。如果我们将它应用到一个指针身上,文本替换出来的结果就是错误的:

  1. int     ar[10];  
  2. int     *p  = ar; // 或 = &ar[0]  
  3. size_t  dim = NUM_ELEMENTS(p); // sizeof(int*) / sizeof(int)!  

结果将是拿sizeof(int)来除sizeof(int*),这在大多数平台上都是1。显然这是错误的(除非p指向的数组碰巧大小是1),并且带有相当程度的误导性。

另一种同样糟糕的情形是,如果该宏被应用到一个用户定义的类身上,并且该类定义了可访问的下标索引操作符(operator [ ]),那么其给出的结果就可能是任意值。事实上,如果该实例的大小小于对它进行下标索引返回的值的大小, 结果就是0,反之结果则大于0,甚至可能出现其结果在交互式调试时恰恰是"对的"情况, 从而给代码作者带来一种"安全"的假象!从这个意义上来说,我宁愿希望编译器在面对使用了这个伪操作符的代码时采取"不予编译"的态度。幸运的是,如果我们接受14.2.1小节的建议,并使用NUM_ELEMENTS()的"逆转式下标索引"形式的话,这个问题就不复存在了。然而,即便你编写的同样用途的宏做到了这一点(绝大多数都做不到),我们仍然还得面对指针的问题,因为指针和数组一样,在两种(正的或反的)下标索引形式下都能够工作。

Imperfection:无法区分数组和指针,会导致对静态数组大小的评估可以被施行到指针或类的身上,从而导致错误的结果。

这个问题的解决方案在于将数组和指针区别对待。直到现在为止这仍然不是完全可行的,但是大多数现代编译器都支持一个可以被用来达此目的的特性。依赖于这样一个事实:从数组到指针的转换(退化)在引用类型的模板实参决议中并不会发生(C++(www.cppentry.com)-98: 14.3.2; [Vand2003, p58])。因而我们就可以定义一个称之为dimensionof()的宏,该宏对于老式编译器而言与NUM_ELEMENTS()的定义一样,但在现代编译器上则实现为一个宏、一个结构以及一个函数的联合体,如下:

  1. template <int N> 
  2. struct array_size_struct  
  3. {  
  4.   byte_t  c[N];  
  5. };  
  6.  
  7. template <class T, int N> 
  8. array_size_struct<N> static_array_size_fn(T (&)[N]);  
  9.  
  10. #define dimensionof(x)     sizeof(static_array_size_fn(x).c)  

其工作原理基本上是这样的:声明(但并不定义)一个模板函数static_array_size_fn(),它接受一个元素类型为T、大小为N的数组的引用,T和N分别作为它的两个模板形参。这样一来,指针类型以及用户自定义类型就被拒之门外了,但这还不足以实现出一个可用的dimensionof()。static_array_size_fn()返回array_sizeof_struct类模板的一个实例,该类模板以static_array_size_fn()接受的数组的大小来参数化,且其内部包含了一个对应大小的字节数组。dimensionof()宏则简单地将sizeof()操作符应用到static_array_size_fn()返回的实例中的数组成员身上,从而得出数组的大小。

一个dimensionof()表达式(不管是使用NUM_ELEMENTS()还是我们引入的新的"宏+函数"形式的dimensionof())总是在编译期被求值。因此,dimensionof()可以被用在常量可以使用的任何地方,例如作为模板实参、数组大小、枚举值,等等。由于C++(www.cppentry.com)标准(C++(www.cppentry.com)-98: 5.3.3)说,sizeof()的操作数不会被求值,所以我们无需定义static_array_size_fn(),从而该设施完全是零代价的。没有任何运行期开销,也不会导致代码膨胀。事实上,这根本就不会生成任何代码!将dimensionof()应用到任何非数组的类型身上会导致编译错误:

  1. int         ai[23];  
  2. int         *pi = ai;  
  3. vector<int> vi(23);  
  4.  
  5. size_t cai = NUM_ELEMENTS(ai); // Ok  
  6. size_t cpi = NUM_ELEMENTS(pi); // 通过编译,但却是错误的!  
  7. size_t cvi = NUM_ELEMENTS(vi); // 同上!  
  8.  
  9. cai = dimensionof(ai); // Ok  
  10. cpi = dimensionof(pi); // 编译报错!这很好。  
  11. cvi = dimensionof(vi); // 同上!  

我得指出,还有一个稍微简洁一点的办法(虽然较难理解)可以用于实现dimensionof(),像这样:
  1. template<typename T, int N> 
  2. byte_t (&byte_array_of_same_dimension_as(T (&)[N]))[N];  
  3.  
  4. #define dimensionof(x)      sizeof(byte_array_of_same_dimension_as((x)));  

遗憾的是,能够正确"理解"以上代码的编译器就更少了, 因此我建议你采用第一种形式。
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇14.2.2 阻止退化 下一篇14.6.3 隐藏向量式new和delete

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: