constexpr和const之间有什么区别?

我什么时候只能使用其中一个?我什么时候可以同时使用这两种方法,我应该如何选择一种?


当前回答

const适用于变量,并防止在代码中修改它们。

constexpr告诉编译器,此表达式会产生编译时常量值,因此可以在数组长度、赋值给常量变量等位置使用。Oli给出的链接有很多很好的示例。

基本上,它们是两个不同的概念,可以(也应该)一起使用。

其他回答

const适用于变量,并防止在代码中修改它们。

constexpr告诉编译器,此表达式会产生编译时常量值,因此可以在数组长度、赋值给常量变量等位置使用。Oli给出的链接有很多很好的示例。

基本上,它们是两个不同的概念,可以(也应该)一起使用。

基本含义和语法

这两个关键字都可以用于对象和函数的声明。应用于对象时的基本区别如下:

const将对象声明为常量。这意味着保证一旦初始化,该对象的值不会改变,编译器可以利用这一事实进行优化。它还有助于防止程序员编写代码来修改初始化后不打算修改的对象。constexpr声明一个对象适合在标准所称的常量表达式中使用。但请注意,constexpr并不是唯一的方法。

应用于函数时,基本区别如下:

const只能用于非静态成员函数,而不能用于一般函数。它保证成员函数不会修改任何非静态数据成员(可变数据成员除外,无论如何都可以修改)。constexpr可以与成员函数和非成员函数以及构造函数一起使用。它声明函数适合在常量表达式中使用。只有当函数满足某些标准(7.1.5/3,4),最重要的是(†),编译器才会接受它:函数体必须是非虚拟的并且非常简单:除了typedef和静态断言之外,只允许一个return语句。对于构造函数,只允许使用初始化列表、typedef和静态断言。(不过,也允许使用=default和=delete。)从C++14开始,规则更加宽松,从那时起,在constexpr函数中允许这样做:asm声明、goto语句、带有除case和default之外的标签的语句、try块、非文字类型变量的定义、静态或线程存储持续时间变量的定义,以及不执行初始化的变量的定义。参数和返回类型必须是文本类型(即,一般来说,非常简单的类型,通常是标量或聚合)

常量表达式

如上所述,constexpr声明了适合在常量表达式中使用的对象和函数。常量表达式不仅仅是常量:

它可以用于需要编译时评估的地方,例如模板参数和数组大小说明符:模板<int N>类fixed_size_list{ /*...*/ };fixed_size_list<X>mylist;//X必须是整数常量表达式整数[X];//X必须是整数常量表达式但请注意:将某物声明为constexpr并不一定保证在编译时对其求值。它可以用于这种情况,但也可以用于运行时评估的其他地方。对象可以适合在常量表达式中使用,而不必声明为constexpr。例子:int main(){常量int N=3;整数[N]={1,2,3};//N是常量表达式}这是可能的,因为N是常量,并且在声明时用文本初始化,满足常量表达式的条件,即使它没有声明为constexpr。

那么我什么时候真的必须使用constexpr?

像上面N这样的对象可以用作常量表达式,而不必声明为constexpr。这适用于以下所有对象:常量整数或枚举类型,以及在声明时使用本身为常量表达式的表达式初始化

[这是由于§5.19/2:常量表达式不能包含涉及“左值到右值修改,除非[…]是整数或枚举类型的glvalue[…]”的子表达式。感谢理查德·史密斯纠正了我之前的说法,即这适用于所有文字类型。]

对于适合在常量表达式中使用的函数,必须显式声明为constexpr;仅仅满足常数表达式函数的标准是不够的。例子:模板<int N>类别列表{ };常量表达式int sqr1(int arg){return arg*arg;}整数sqr2(整数参数){return arg*arg;}int main(){常量int X=2;列表<sqr1(X)>mylist1;//确定:sqr1是常量表达式列表<sqr2(X)>mylist2;//错误:sqr2不是constexpr}

我什么时候可以/应该同时使用const和constexpr?

A.在对象声明中。当两个关键字都引用要声明的同一对象时,这从来都不是必需的。constexpr表示常量。

constexpr const int N = 5;

constexpr int N = 5;

但是,请注意,在某些情况下,每个关键字都引用声明的不同部分:

static constexpr int N = 3;

int main()
{
  constexpr const int *NP = &N;
}

这里,NP被声明为一个地址常量表达式,即一个本身就是常量表达式的指针。(当通过将地址运算符应用于静态/全局常量表达式来生成地址时,这是可能的。)在这里,constexpr和const都是必需的:constexpr始终引用要声明的表达式(这里是NP),而const引用int(它声明指向const的指针)。删除常量将使表达式非法(因为(a)指向非常量对象的指针不能是常量表达式,而(b)&N实际上是指向常量的指针)。

B.在成员函数声明中。在C++11中,constexpr表示const,而在C++14和C++17中则不是这样。在C++11中声明为

constexpr void f();

需要声明为

constexpr void f() const;

以便仍然可用作常量函数。

根据Bjarne Stroustrup的《C++编程语言第四版》一书•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

函数在常量表达式中可用,即在要计算的表达式中编译器必须将其定义为constexpr。例如:

constexpr double square(double x) { return x∗x; }

要成为constexpr,函数必须相当简单:只需要一个计算值的return语句。A.constexpr函数可以用于非常量参数,但当这样做时,结果不是常量表达式。我们允许使用非常量表达式参数调用constexpr函数在不需要常量表达式的上下文中,这样我们就不需要定义相同的函数两次:一次用于常量表达式,一次用于变量。在一些地方,语言规则需要常量表达式(例如,数组边界(§2.2.5,§7.3)、案例标签(§2.2.4、§9.4.2)、一些模板参数(§25.2)以及使用常量表达式)。在其他情况下,编译时评估对性能很重要。独立于性能问题,不变性(具有不可更改状态的对象)的概念是重要设计问题(§10.4)。

概述

const保证程序不会更改对象的值。但是,const不能保证对象经历哪种类型的初始化。考虑:const int mx=numeric_limits<int>::max();//OK:运行时初始化函数max()只返回一个文字值。然而,由于初始值设定项是函数调用,mx会进行运行时初始化。因此,不能将其用作常量表达式:整数arr[mx];//错误:“需要常量表达式”constexpr是一个新的C++11关键字,它使您无需创建宏和硬编码文字。它还保证在某些条件下,对象进行静态初始化。它控制表达式的求值时间。通过强制对其表达式进行编译时求值,constexpr允许您在依赖编译时常量的任何代码中定义对时间关键型应用程序、系统编程、模板以及一般来说至关重要的真正常量表达式。

常量表达式函数

常量表达式函数是声明为constexpr的函数。它的主体必须是非虚拟的,除了typedef和静态断言之外,它只能由一个返回语句组成。其参数和返回值必须具有文本类型。它可以与非常量表达式参数一起使用,但当这样做时,结果不是常量表达式。

常量表达式函数旨在替换宏和硬编码文本,而不牺牲性能或类型安全性。

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)

常量表达式对象

常量表达式对象是声明为constexpr的对象。必须使用常量表达式或由带有常量表达式参数的常量表达式构造函数构造的右值来初始化它。

常量表达式对象的行为就像声明为常量一样,除了它需要在使用之前进行初始化,并且其初始值设定项必须是常量表达式。因此,常量表达式对象始终可以用作另一个常量表达式的一部分。

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

常量表达式构造函数

常量表达式构造函数是声明为constexpr的构造函数。它可以有一个成员初始化列表,但除了typedef和静态断言之外,它的主体必须为空。其参数必须具有文字类型。

常量表达式构造函数允许编译器在编译时初始化对象,前提是构造函数的参数都是常量表达式。

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对象是常量,并用编译期间已知的值初始化;constexpr函数在使用编译期间已知值的参数调用时产生编译时结果;constexpr对象和函数可以在比非constexpr的对象和函数更广泛的上下文中使用;constexpr是对象或函数接口的一部分。

资料来源:在C++中使用constexpr提高安全性、性能和封装。

正如@0x499602d2已经指出的,const只确保初始化后不能更改值,而constexpr(在C++11中引入)保证变量是编译时常量。考虑以下示例(来自LearnApp.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