Const和Const有什么区别?

constexprconst有什么区别?

  • 我什么时候可以只使用其中一个?
  • 我什么时候可以同时使用两者,我应该如何选择一个?
306190 次浏览

const适用于代码中的变量防止它们被修改

constexpr告诉编译器这个表达式结果是编译时间常数值,所以它可以用于数组长度、分配const变量等地方。Oli给出的链接有很多很好的例子。

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

基本含义和语法

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

  • const将一个对象声明为恒定。这意味着保证一旦初始化,该对象的值不会改变,编译器可以利用这一事实进行优化。它还有助于防止程序员编写修改初始化后不打算修改的对象的代码。

  • constexpr声明一个对象适合标准调用常量表达式。但请注意,constexpr不是做到这一点的唯一方法。

当应用于功能时,基本区别是这样的:

  • const只能用于非静态成员函数,而不是一般的函数。它保证成员函数不会修改任何非静态数据成员(可变数据成员除外,无论如何都可以修改)。

  • constexpr可以与成员和非成员函数以及构造函数一起使用。它声明函数适合在常量表达式中使用。编译器只有在函数满足某些条件(7.1.5/3,4)时才会接受它,最重要的是()

    • 函数体必须是非虚的并且非常简单:除了typedef和静态断言,只允许一个return语句。对于构造函数,只允许初始化列表、typedef和静态断言。(不过,也允许= default= delete。)
    • 从C++14开始,规则变得更加宽松,从那时起,在constexr函数中允许什么:asm声明,goto语句,带有casedefault以外标签的语句,try块,非文字类型变量的定义,静态或线程存储持续时间变量的定义,不执行初始化的变量的定义。
    • 参数和返回类型必须是文字类型(即,一般来说,非常简单的类型,通常是标量或聚合)

常量表达式

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

  • 它可以用于需要编译时计算的地方,例如模板参数和数组大小说明符:

      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并不一定保证它将在编译时被计算。它可以使用就是这样,但它也可以在运行时计算的其他地方使用。

  • 对象可能适合在声明为constexpr的常量表达式没有中使用。示例:

         int main(){const int N = 3;int numbers[N] = {1, 2, 3};  // N is constant expression}

    这是可能的,因为N是常量并在声明时使用文字初始化,满足常量表达式的条件,即使它没有声明constexpr

那么什么时候需要使用constexpr呢?

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

[这是由于§5.19/2:常量表达式不能包含涉及“左值-右值修改,除非[…]整数或枚举类型的glvalue[…]”的子表达式,感谢Richard Smith纠正了我之前的说法,即所有文字类型都是如此。

  • 为了使函数适合在常量表达式中使用,它必须被显式声明为constexpr;它仅仅满足常量表达式函数的标准是不够的。例子:

     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 constexprlist<sqr2(X)> mylist2;  // wrong: sqr2 is not constexpr}

我什么时候可以/应该同时使用constconstexpr

A.在对象声明中。当两个关键字都指向要声明的同一个对象时,这是不必要的。constexpr暗示const

constexpr const int N = 5;

constexpr int N = 5;

但是,请注意,可能存在关键字各自引用声明的不同部分的情况:

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

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

B.在成员函数声明中。在C++11中,constexpr意味着const,而在C++14和C++17中并非如此

constexpr void f();

需要宣布为

constexpr void f() const;

根据C++14,以便仍然可用作const函数。

根据Bjarne Stroustrup的《C++编程语言第四版》一书
const:大致意思是“我保证不改变这个值”(§7.5)。这主要用于指定接口,以便可以将数据传递给函数而不必担心它被修改。
编译器强制执行const所做的承诺。
CONSTEXR:大致意味着“在编译时进行评估”(§10.4)。这主要用于指定常量,以允许
例如:

const int dmv = 17; // dmv is a named constantint var = 17; // var is not a constantconstexpr double max1 = 1.4*square(dmv); // OK if square(17) is a constant expressionconstexpr double max2 = 1.4∗square(var); // error : var is not a constant expressionconst double max3 = 1.4∗square(var); //OK, may be evaluated at run timedouble 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 constantconst double s1 = sum(v); // OK: evaluated at run timeconstexpr double s2 = sum(v); // error : sum(v) not constant expression

对于在常量表达式中可用的函数,即在将被计算的表达式中由编译器定义,它必须定义为CONSTEXR
例如:

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


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

概览

  • const保证程序不会改变对象的值。但是,const不保证对象经历哪种类型的初始化。

    考虑:

    const int mx = numeric_limits<int>::max();  // OK: runtime initialization

    函数max()仅返回一个文字值。然而,因为初始化器是一个函数调用,mx经历了运行时初始化。因此,您不能将其用作常数表达式

    int arr[mx];  // error: “constant expression required”
  • constexpr is a new C++11 keyword that rids you of the need to create macros and hardcoded literals. It also guarantees, under certain conditions, that objects undergo static initialization. It controls the evaluation time of an expression. By enforcing compile-time evaluation of its expression, constexpr lets you define true constant expressions that are crucial for time-critical applications, system programming, templates, and generally speaking, in any code that relies on compile-time constants.

Constant-expression functions

A constant-expression function is a function declared constexpr. Its body must be non-virtual and consist of a single return statement only, apart from typedefs and static asserts. Its arguments and return value must have literal types. It can be used with non-constant-expression arguments, but when that is done the result is not a constant expression.

A constant-expression function is meant to replace macros and hardcoded literals without sacrificing performance or type safety.

constexpr int max() { return INT_MAX; }           // OKconstexpr long long_max() { return 2147483647; }  // OKconstexpr 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 expressionconst 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的对象。它必须使用常量表达式或由常量表达式构造函数使用常量表达式参数构造的右值进行初始化。

常量表达式对象的行为就像它被声明为const一样,只是它需要在使用前初始化并且它的初始化器必须是常量表达式。因此,常量表达式对象始终可以用作另一个常量表达式的一部分。

struct S{constexpr int two();      // constant-expression functionprivate:static constexpr int sz;  // constant-expression object};constexpr int S::sz = 256;enum DataPacket{Small = S::two(),  // error: S::two() called before it was definedBig = 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 constructorconstexpr complex(double r, double i) : re(r), im(i) { }  // OK: empty body// constant-expression functionsconstexpr double real() { return re; }constexpr double imag() { return im; }private:double re;double im;};constexpr complex COMP(0.0, 1.0);         // creates a literal complexdouble x = 1.0;constexpr complex cx1(x, 0);              // error: x is not a constant expressionconst complex cx2(x, 1);                  // OK: runtime initializationconstexpr double xx = COMP.real();        // OK: compile-time initializationconstexpr double imaglval = COMP.imag();  // OK: compile-time initializationcomplex cx3(2, 4.6);                      // OK: runtime initialization

来自Scott Meyers关于constexpr的书有效的现代C++的提示:

  • constexpr对象是const,并使用编译期间已知的值初始化;
  • constexpr函数在使用其值在编译期间已知的参数调用时产生编译时结果;
  • constexpr对象和函数可以比非constexpr对象和函数在更广泛的上下文中使用;
  • constexpr是对象或函数接口的一部分。

来源:在C++中使用Constrexr提高安全性、性能和封装.

正如@0x499602d2已经指出的那样,const仅确保初始化后不能更改值,而constexpr(在C++11中引入)保证变量是编译时常量。
考虑以下示例(来自LearnCpp.com):

cout << "Enter your age: ";int age;cin >> age;
const int myAge{age};        // worksconstexpr int someAge{age};  // error: age can only be resolved at runtime

const int var可以在运行时动态设置为一个值,一旦设置为该值,就不能再更改它。

constexpr int var不能在运行时动态设置,而是在编译时动态设置。一旦它设置为该值,就不能再更改它。

这里有一个坚实的例子:

int main(int argc, char*argv[]) {const int p = argc;// p = 69; // cannot change p because it is a const// constexpr int q = argc; // cannot be, bcoz argc cannot be computed at compile timeconstexpr int r = 2^3; // this works!// r = 42; // same as const too, it cannot be changed}

上面的代码片段编译得很好,我已经注释掉了那些导致它出错的代码。

这里需要注意的关键概念是compile timerun time的概念。C++引入了新的创新,旨在尽可能多地在编译时** know **某些事情,以提高运行时的性能。

任何不涉及上述两个关键概念的解释尝试都是幻觉。

constconstexpr都可以应用于变量和函数。尽管它们彼此相似,但实际上它们是非常不同的概念。

constconstexpr都表示它们的值在初始化后不能更改。例如:

const int x1=10;constexpr int x2=10;
x1=20; // ERROR. Variable 'x1' can't be changed.x2=20; // ERROR. Variable 'x2' can't be changed.

constconstexpr之间的主要区别是它们的初始化值已知(评估)的时间。虽然const变量的值可以在编译时和运行时评估,但constexpr总是在编译时评估。例如:

int temp=rand(); // temp is generated by the the random generator at runtime.
const int x1=10; // OK - known at compile time.const int x2=temp; // OK - known only at runtime.constexpr int x3=10; // OK - known at compile time.constexpr int x4=temp; // ERROR. Compiler can't figure out the value of 'temp' variable at compile time so `constexpr` can't be applied here.

知道该值是在编译时还是在运行时已知的关键优势是可以在需要编译时常量时使用编译时常量。例如,C++不允许您指定具有可变长度的C数组。

int temp=rand(); // temp is generated by the the random generator at runtime.
int array1[10]; // OK.int array2[temp]; // ERROR.

所以这意味着:

const int size1=10; // OK - value known at compile time.const int size2=temp; // OK - value known only at runtime.constexpr int size3=10; // OK - value known at compile time.

int array3[size1]; // OK - size is known at compile time.int array4[size2]; // ERROR - size is known only at runtime time.int array5[size3]; // OK - size is known at compile time.

所以const变量既可以定义编译时间常量,就像size1一样,可以用来指定数组大小,也可以定义运行时常量,就像size2一样,只能在运行时知道,不能用来定义数组大小。另一方面,constexpr总是定义可以指定数组大小的编译时常量。

constconstexpr也可以应用于函数。const函数必须是成员函数(方法、运算符),其中应用const关键字意味着方法不能更改其成员(非静态)字段的值。例如。

class test{int x;
void function1(){x=100; // OK.}
void function2() const{x=100; // ERROR. The const methods can't change the values of object fields.}};

constexpr是一个不同的概念。它将一个函数(成员或非成员)标记为可以在编译时如果编译时常量作为其参数传递评估的函数。例如,您可以这样写。

constexpr int func_constexpr(int X, int Y){return(X*Y);}
int func(int X, int Y){return(X*Y);}
int array1[func_constexpr(10,20)]; // OK - func_constexpr() can be evaluated at compile time.int array2[func(10,20)]; // ERROR - func() is not a constexpr function.
int array3[func_constexpr(10,rand())]; // ERROR - even though func_constexpr() is the 'constexpr' function, the expression 'constexpr(10,rand())' can't be evaluated at compile time.

顺便说一句,constexpr函数是常规的C++函数,即使传递了非常量参数也可以调用。但在这种情况下,您将获得非constexr值。

int value1=func_constexpr(10,rand()); // OK. value1 is non-constexpr value that is evaluated in runtime.constexpr int value2=func_constexpr(10,rand()); // ERROR. value2 is constexpr and the expression func_constexpr(10,rand()) can't be evaluated at compile time.

constexpr也可以应用于成员函数(方法)、运算符甚至构造函数。例如。

class test2{static constexpr int function(int value){return(value+1);}
void f(){int x[function(10)];

}};

一个更“疯狂”的样本。

class test3{public:
int value;
// constexpr const method - can't chanage the values of object fields and can be evaluated at compile time.constexpr int getvalue() const{return(value);}
constexpr test3(int Value): value(Value){}};

constexpr test3 x(100); // OK. Constructor is constexpr.
int array[x.getvalue()]; // OK. x.getvalue() is constexpr and can be evaluated at compile time.

首先,两者都是c++中的限定符。声明为const的变量必须初始化,并且将来不能更改。因此,通常声明为const的变量甚至在编译之前就会有一个值。

但是,对于constexr来说,这有点不同。

对于constrexr,您可以给出一个表达式,该表达式可以在程序编译期间进行评估。

显然,声明为const的变量将来不能像const一样更改。

我不认为任何答案都能确切地说明它有什么副作用,或者实际上,它是什么。

当使用文字或表达式初始化时,命名空间/文件范围中的constexprconst是相同的;但是对于一个函数,const可以被任何函数初始化,但是constexpr被一个非参数初始化(一个没有标记为参数的函数或一个非参数表达式)会产生一个编译器错误。constexprconst都是变量的隐式内部链接(实际上,如果编译-O1和更强,它们无法存活到链接阶段,并且static不会强制编译器在-O1或更强时为constconstexpr发出内部(本地)链接器符号;它唯一这样做的时候是如果您获取变量的地址。在函数上,constexpr使函数永远不会达到链接阶段(无论定义中的const1或const5,还是-O0或-OFast),而const永远不会,staticconst5仅对-O1及更高版本有此影响。当constexpr函数初始化const/constexpr变量时,负载总是使用任何优化标志进行优化,但如果函数仅为staticconst5,或者变量不是const/constexpr,则永远不会优化。

标准编译(-O0)

#include<iostream>constexpr int multiply (int x, int y){
return x * y;}
extern const int val = multiply(10,10);int main () {std::cout << val;}

编译成

val:.long   100  //extra external definition supplied due to extern
main:push    rbpmov     rbp, rspmov     esi, 100 //substituted in as an immediatemov     edi, OFFSET FLAT:_ZSt4coutcall    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)mov     eax, 0pop     rbpret
__static_initialization_and_destruction_0(int, int):...

然而

#include<iostream>const int multiply (int x, int y){
return x * y;}
const int val = multiply(10,10); //constexpr is an errorint main () {std::cout << val;}

编译为

multiply(int, int):push    rbpmov     rbp, rspmov     DWORD PTR [rbp-4], edimov     DWORD PTR [rbp-8], esimov     eax, DWORD PTR [rbp-4]imul    eax, DWORD PTR [rbp-8]pop     rbpret
main:push    rbpmov     rbp, rspmov     eax, DWORD PTR val[rip]mov     esi, eaxmov     edi, OFFSET FLAT:_ZSt4coutcall    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)mov     eax, 0pop     rbpret
__static_initialization_and_destruction_0(int, int):...mov     esi, 10mov     edi, 10call    multiply(int, int)mov     DWORD PTR val[rip], eax

这清楚地表明,constexpr导致const/constexpr文件范围变量的初始化发生在编译时并且不会产生全局符号,而不使用它会导致初始化在运行时发生在main之前。

使用-OFast编译

即使-OFast也无法优化负载!https://godbolt.org/z/r-mhif,所以你需要constexpr


constexpr函数也可以从其他constexpr函数中调用以获得相同的结果。函数上的constexpr也可以防止在函数中使用编译时无法完成的任何事情;例如,调用std::cout上的<<运算符。

块作用域中的constexpr的行为与此相同,因为它在由非constexr函数初始化时产生错误;该值也立即被替换。

最后,它的主要用途就像C的内联函数,但它只有在函数用于初始化文件范围变量时才有效(函数不能在C上执行,但它们可以在C++上执行,因为它允许文件范围变量的动态初始化),除了函数不能将全局/局部符号导出到链接器,即使使用extern/static,您也可以在C上使用inline;块范围变量赋值函数可以简单地使用-O1优化内联,而无需在C和C++上使用constexpr

关于const关键字的概述

在C++中,如果const对象是用常量表达式初始化的,我们可以在任何需要常量表达式的地方使用我们的const对象。

const int x = 10;int a[x] = {0};

例如,我们可以在Switch中创建case语句。

Constexr可以与数组一起使用。

Constexr不是一个类型。

Constexr关键字可以与自动关键字结合使用。

constexpr auto x = 10;
struct Data {   // We can make a bit field element of struct.int a:x;};

如果我们使用常量表达式初始化const对象,则该const对象生成的表达式现在也是常量表达式。

常量表达式:一个表达式,其值可以在编译时计算。

x*5-4//这是一个常量表达式。对于编译器来说,键入这个表达式和直接键入46没有区别。

初始化是强制性的。它只能用于阅读目的。它不能被更改。到目前为止,“const”和“Constexr”关键字之间没有区别。

注:我们可以在同一个声明中使用Const和Const。

constexpr const int* p;

constextr函数

通常,函数的返回值是在运行时获得的。但是,当满足某些条件时,将在编译时以常量的形式获得对Constexr函数的调用。

注:参数在函数调用中发送到函数的参数变量或所有参数变量,如果有多个参数,如果C. E函数的返回值将在编译时计算。

constexpr int square (int a){return a*a;}
constexpr int a = 3;constexpr int b = 5;
int arr[square(a*b+20)] = {0}; //This expression is equal to int arr[35] = {0};

为了使函数成为Constexr函数,函数的返回值类型和函数参数的类型必须在称为“文字类型”的类型类别中。

Constexr函数是隐式内联函数。

重要一点:

没有一个康斯派克函数需要用常量表达式调用。它不是强制性的。如果发生这种情况,计算将不会在编译时完成。它将被视为正常的函数调用。因此,在需要常量表达式的地方,我们将不再能够使用这个表达式。

需要的条件是一个constexr函数如下所示;

1)函数参数中使用的类型和函数返回值的类型必须是文字类型。

2)具有静态生命周期的局部变量不应在函数内部使用。

3)如果函数是合法的,当我们在编译时用常量表达式调用这个函数时,编译器会在编译时计算函数的返回值。

4)编译器需要查看函数的代码,因此constexr函数几乎总是在头文件中。

5)为了使我们创建的函数成为Constexr函数,函数的定义必须在头文件中。因此,无论哪个源文件包含该头文件,都将看到函数定义。

奖金

通常使用默认成员初始化,可以在类中初始化具有const和整数类型的静态数据成员。然而,为了做到这一点,必须同时存在“const”和“整数类型”。

如果我们使用静态constexr,那么它不必是整数类型来在类内初始化它。只要我用常量表达式初始化它,就没有问题。

class Myclass  {const static int sx = 15;         // OKconstexpr static int sy = 15;     // OKconst static double sd = 1.5;     // ERRORconstexpr static double sd = 1.5; // OK};