有没有可能,将Item 15为constexpr?
摘要:本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢! 博客已经迁移到 "这里啦" 如果说C&a
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!
博客已经迁移到这里啦
如果说C++11中有什么新东西能拿“最佳困惑奖”的话,那肯定是constexpr了。当把它用在对象上时,它本质上是const的加强版,但是把它用在函数上时,它将拥有不同的意义。切开“迷雾”(解开困惑)是值得的,因为当constexpr符合你想表达的情况时,你肯定会想要使用它的。
从概念上来说,constexpr表明的一个值不只是不变的,它还能在编译期被知道。但是这个概念只是故事的一部分,因为当constexpr应用在函数上时,事情变得比看上去还要微妙。为了避免毁掉后面的惊喜,现在,我只能说你不能假设constexpr函数的返回值是const的,同时你也不能假设这些值能在编译期被知道。也许最有趣的是,这些东西都是特性(是有用的)。对于constexpr函数来说,不需要产生const或能在编译期知道的返回结果是一件好事。
但是,让我们从constexpr对象开始。这些对象确实是常量,也确实能在编译期被知道。(技术上来讲,它们的值是在翻译阶段被决定的,翻译阶段包含了编译期和链接期。除非你要写一个C++的编译器或连接器,不然这都影响不到你,所以你能在编程的时候,开心地假设为constexpr对象的值是在编译期被决定的)
值能在编译器知道是很有用的。它们能代替只读内存,举个例子,尤其是对一些嵌入式系统来说,这是一个相当重要的特性。更广泛的应用就是,当C++要求一个不变的,并且在编译期能知道的整形常量表达式的时候,我们可以使用它(constexpr对象或函数)来替代。这样的场景包括了数组大小的说明,整形模板参数(包括std::array对象的长度),枚举成员的值,alignment说明符,等等。如果你想使用变量来做这些事,你肯定想要把它声明为constexpr,因为编译器会确保它是一个编译期的值:
int sz; //non-constexpr变量
...
constexpr auto arraySize1 = sz; //错误!sz的值不是在编译期被知道的
std::array<int, sz> data1; //错误!同样的问题
constexpr auto arraySize2 = 10; //对的,10是一个编译期的常量
std::array<int, arraySize2> data2; //对的,arraySize2是一个constexpr
记住,const不能提供和constexpr一样的保证,因为const对象不需要用“在编译期就知道的”值初始化:
int sz;
...
const auto arraySize = sz; //对的,arraySize是拷贝自sz的const变量
std::array<int, arraySize> data; //错误!arraySize的值不能在编译期知道
简单来说,所有的constexpr对象都是const对象,但是不是所有的const对象都是constexpr对象。如果你想让编译器保证变量拥有的值能被用在那些,需要编译期常量的上下文中,那么你就应该使用constexpr而不是const。
当涉及constexpr函数时,constexpr对象的使用范围变得更加有趣。当使用编译期常量来调用这样的函数时,它们产生编译期常量。当用来调用函数的值不能在运行期前得知时,它们产生运行期的值。这听起来好像你知道它们会做什么,但是这么想是错误的。正确的观点是这样的:
constexpr函数能被用在要求编译期常量的上下文中,如果所有传入constexpr函数的参数都能在编译期知道,那么结果将在编译期计算出来。如果有任何一个参数的值不能在编译期知道,你的代码就被拒绝(不能在编译期执行)了。
当使用一个或多个不能在编译期知道的值来调用一个constexpr函数时,它表现得就像一个正常的函数,在运行期计算它的值。这意味着你不需要两个函数来表示相同的操作,一个为编译期常量服务,一个为所有的值服务。constexpr函数把这些事都做了。
假设你需要一个数据结构来存放一个运算方式不会改变的实验结果。举个例子,在实验过程中,灯的亮度等级(有高,低,关三种状态),风扇的速度,以及温度也是这样,等等。如果这里有n种环境条件和实验有关,每种条件有三种状态,那么结果的组合数量就是3n。因此对于实验结果的所有的组合进行保存,就需要一个起码有3n的空间的数据结构。假设每个结果都是一个int,那么n就是在编译期已知的(或者说可以计算出来),std::array是一个合理的选择。
