`constexpr`和`const`之间的区别
constexpr
和const
什么区别?
- 我什么时候可以只使用其中一个?
- 我什么时候可以使用,我应该如何select?
基本含义和语法
这两个关键字都可以在对象声明和函数中使用。 应用于对象的基本区别是:
-
const
声明一个对象为常量 。 这意味着一个保证,一旦初始化,该对象的值不会改变,编译器可以利用这个事实进行优化。 它还有助于防止程序员编写修改初始化后不打算修改的对象的代码。 -
constexpr
声明一个对象适合用在标准调用常量expression式的地方 。 但请注意,constexpr
不是唯一的方法来做到这一点。
当应用于function的基本差异是这样的:
-
const
只能用于非静态成员函数,而不是一般的函数。 它保证了成员函数不会修改任何非静态数据成员。 -
constexpr
可以与成员和非成员函数以及构造函数一起使用。 它声明了适用于常量expression式的函数。 如果函数符合某些标准(7.1.5 / 3,4),编译器将只接受它,最重要的是(†) :- 函数体必须是非虚拟的,非常简单:除typedef和静态断言之外,只允许一个
return
语句。 在构造函数的情况下,只允许初始化列表typedefs和静态声明。 (= default
和= delete
也是允许的,但是。 - 参数和返回types必须是文字types (即一般来说,非常简单的types,通常是标量或集合)
- 函数体必须是非虚拟的,非常简单:除typedef和静态断言之外,只允许一个
常量expression式
如上所述, constexpr
声明了两个对象以及适合在常量expression式中使用的函数。 一个恒定的expression不仅仅是不变的:
-
它可用于需要编译时评估的地方,例如模板参数和数组大小说明符:
template<int N> class fixed_size_list { /*...*/ }; fixed_size_list<X> mylist; // X must be an integer constant expression int numbers[X]; // X must be an integer constant expression
-
但是请注意:
-
声明为
constexpr
并不一定保证在编译时进行评估。 它可以用于此类,但也可以在运行时评估的其他地方使用。 -
一个对象可能适合在常量expression式中使用, 而不被声明为
constexpr
。 例:int main() { const int N = 3; int numbers[N] = {1, 2, 3}; // N is constant expression return 0; }
这是可能的,因为
N
是常量,并在声明时用文字初始化,即使它没有被声明为constexpr
,它也满足常量expression式的标准。 -
那么我什么时候需要使用constexpr
?
-
像上面的
N
这样的对象可以被用作常量expression式而不被声明为constexpr
。 所有对象都是如此:-
const
- 积分或枚举types和
- 在声明时用一个本身就是常量expression式的expression式进行初始化
[这是由于§5.19/ 2:一个常量expression式不能包括一个子expression式,涉及“左值,右值修改,除非[…]整数或枚举types的glvalue”感谢理查史密斯纠正我早些时候声称这对所有字面types都是正确的。]
-
-
对于适合在常量expression式中使用的函数 , 必须明确声明为
constexpr
; 仅仅满足常量expression函数的标准是不够的。 例:template<int N> class list { }; constexpr int sqr1(int arg) { return arg * arg; } int sqr2(int arg) { return arg * arg; } int main() { const int X = 2; list<sqr1(X)> mylist1; // OK: sqr1 is constexpr list<sqr2(X)> mylist2; // wrong: sqr2 is not constexpr return 0; }
我什么时候可以同时使用const
和constexpr
?
A.在对象声明中。 当两个关键字都指向同一个被声明的对象时,这是不必要的。 constexpr
意味着const
。
constexpr const int N = 5;
是相同的
constexpr int N = 5;
但是,请注意,可能会出现以下情况:关键字分别指向声明的不同部分:
static constexpr int N = 3; int main() { constexpr const int *NP = &N; return 0; }
在这里, NP
被声明为一个地址常量expression式,也就是一个本身就是常量expression式的指针。 (通过将地址运算符应用于静态/全局常量expression式来生成地址时,这是可能的。)这里, constexpr
和const
都是必需的: constexpr
总是指正在声明的expression式(这里是NP
),而const
是指int
(它声明了一个指向const的指针)。 删除const
会导致expression式不合法(因为(a)非const对象的指针不能是一个常量expression式,而(b) &N
实际上是一个指向常量的指针)。
B.在成员函数声明中。 在C ++ 11中, constexpr
隐含const
也用于成员函数。 但是,这在C ++ 14中可能会改变。 根据目前的草案, constexpr
将暗示只对于对象 ,而不是成员职能,因为build议修改§7.1.5/ 8。 因此,在C ++ 11下声明一个成员函数为
constexpr void f();
将不得不被宣布为
constexpr void f() const;
在C ++ 14下,以便仍然可以用作const
函数。 最好把你的constexpr
成员函数标记为const
,以避免稍后改变很多代码。
(†)可接受的constexpr
函数的条件可能会放宽C ++ 14。 理查德·史密斯(Richard Smith)的一项提案最近已被纳入C ++ 14草案 。
const
适用于variables ,并防止它们在代码中被修改 。
constexpr
告诉编译器,这个expression式产生一个编译时间常量值 ,所以它可以用在像数组长度,赋值给const
variables等等的地方。Oli给出的链接有很多很好的例子。
基本上他们是两个不同的概念,可以(也应该)一起使用。
概观
-
const
保证程序不会改变对象的值 。 但是,const
不能保证对象经历了哪种types的初始化。考虑:
const int mx = numeric_limits<int>::max(); // OK: runtime initialization
函数
max()
仅仅返回一个文字值。 但是,因为初始化程序是一个函数调用,mx
会进行运行时初始化。 因此,您不能将其用作常量expression式 :int arr[mx]; // error: “constant expression required”
-
constexpr
是一个新的C + + 11关键字,摆脱了你需要创buildmacros和硬编码文字。 它还保证,在一定的条件下,对象进行静态初始化 。 它控制expression式的评估时间。 通过对其expression式进行编译时评估 ,constexpr
允许您定义真正的常量expression式 ,这些expression式对于时间关键型应用程序,系统编程,模板以及一般来说在任何依赖于编译时常量的代码中都是至关重要的。
常量expression式函数
常量expression式函数是一个声明为constexpr
的函数。 它的主体必须是非虚拟的,除了typedef和静态断言之外,它只包含一个返回语句。 它的参数和返回值必须有文字types。 它可以与非常量expression式参数一起使用,但是当完成时,结果不是一个常量expression式。
常量expression式函数意味着replacemacros和硬编码文字而不牺牲性能或types安全性。
constexpr int max() { return INT_MAX; } // OK constexpr long long_max() { return 2147483647; } // OK constexpr bool get_val() { bool res = false; return res; } // error: body is not just a return statement constexpr int square(int x) { return x * x; } // OK: compile-time evaluation only if x is a constant expression const int res = square(5); // OK: compile-time evaluation of square(5) int y = getval(); int n = square(y); // OK: runtime evaluation of square(y)
常量expression式对象
常量expression式对象是一个声明为constexpr
的对象。 它必须用一个常量expression式或一个由具有常量expression式参数的常量expression式构造函数构造的右值进行初始化。
一个常量expression式对象的行为就好像它被声明为const
,只不过它在使用之前需要初始化,并且它的初始值设定项必须是一个常量expression式。 因此,常量expression式对象总是可以用作另一个常量expression式的一部分。
struct S { constexpr int two(); // constant-expression function private: static constexpr int sz; // constant-expression object }; constexpr int S::sz = 256; enum DataPacket { Small = S::two(), // error: S::two() called before it was defined Big = 1024 }; constexpr int S::two() { return sz*2; } constexpr S s; int arr[s.two()]; // OK: s.two() called after its definition
常量expression式构造函数
常量expression式构造函数是一个声明为constexpr
的构造函数。 它可以有一个成员初始化列表,但它的主体必须是空的,除了typedef和静态断言。 它的参数必须有文字types。
一个常量expression式构造函数允许编译器在编译时初始化对象,前提是构造函数的参数都是常量expression式。
struct complex { // constant-expression constructor constexpr complex(double r, double i) : re(r), im(i) { } // OK: empty body // constant-expression functions constexpr double real() { return re; } constexpr double imag() { return im; } private: double re; double im; }; constexpr complex COMP(0.0, 1.0); // creates a literal complex double x = 1.0; constexpr complex cx1(x, 0); // error: x is not a constant expression const complex cx2(x, 1); // OK: runtime initialization constexpr double xx = COMP.real(); // OK: compile-time initialization constexpr double imaglval = COMP.imag(); // OK: compile-time initialization complex cx3(2, 4.6); // OK: runtime initialization
Scott Meyers撰写的“ 有效的现代C ++ ”一书中有关constexpr
:
-
constexpr
对象是const的,并且在编译期间用已知的值初始化; -
constexpr
函数在编译期间通过其值已知的参数调用时产生编译时结果; -
constexpr
对象和函数可以用在比非constexpr
对象和函数更广泛的上下文中; -
constexpr
是对象或函数接口的一部分。
来源: 使用constexpr来提高C ++中的安全性,性能和封装 。
根据Bjarne Stroustrup的“The C ++ Programming Language 4th Editon”一书
• const :大致意思是“我保证不改变这个值”(§7.5)。 这主要用于指定接口,以便数据可以传递给函数,而不用担心被修改。
编译器强制执行const所做的承诺。
• constexpr :大致意思是“在编译时进行评估”(第10.4节)。 这主要用于指定常量,以允许
例如:
const int dmv = 17; // dmv is a named constant int var = 17; // var is not a constant constexpr double max1 = 1.4*square(dmv); // OK if square(17) is a constant expression constexpr double max2 = 1.4∗square(var); // error : var is not a constant expression const double max3 = 1.4∗square(var); //OK, may be evaluated at run time double sum(const vector<double>&); // sum will not modify its argument (§2.2.5) vector<double> v {1.2, 3.4, 4.5}; // v is not a constant const double s1 = sum(v); // OK: evaluated at run time constexpr double s2 = sum(v); // error : sum(v) not constant expression
要使函数在常量expression式中可用,即在由编译器评估的expression式中,必须定义constexpr 。
例如:
constexpr double square(double x) { return x∗x; }
一个函数必须非常简单,只需要一个返回语句来计算一个值。 一个constexpr函数可以用于非常量参数,但是当这样做结果不是一个常量expression式。 我们允许在不需要常量expression式的上下文中使用非常量expression式参数来调用constexpr函数,这样我们就不需要两次定义实质上相同的函数:一次用于常量expression式,一次用于variables。
在一些地方,语言规则(例如,数组边界(§2.2.5,§7.3),案例标签(§2.2.4,§9.4.2),一些模板参数(§25.2)和常数使用constexpr声明)。 在其他情况下,编译时评估对性能很重要。 独立于性能问题,不变性(不可改变状态的对象)的概念是一个重要的devise问题(§10.4)。
正如@ 0x499602d2已经指出的那样, const
只能确保一个值在初始化后不能被改变,因为constexpr
(在C ++ 11中引入)保证这个variables是一个编译时间常量。
考虑下面的例子(来自LearnCpp.com):
cout << "Enter your age: "; int age; cin >> age; const int myAge{age}; // works constexpr int someAge{age}; // error: age can only be resolved at runtime