关于c ++:`constexpr`和`const`之间的区别

Difference between `constexpr` and `const`

constexprconst有什么区别?

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


基本含义和语法

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

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

  • constexpr声明一个对象适合在标准称为常量表达式的对象中使用。但请注意,constexpr并不是唯一的方法。好的。

当应用于函数时,基本区别在于:好的。

  • const只能用于非静态成员函数,一般不能用于函数。它保证成员函数不会修改任何非静态数据成员。好的。

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

    • 函数体必须是非虚拟的且非常简单:除了typedef和静态断言之外,只允许使用单个return语句。对于构造函数,只允许初始化列表、typedef和静态断言。(不过,也允许使用= default= delete。)
    • 在C++ 14中,规则是更宽松的,此后在CONTHEXPR函数中允许的是:EDCOX1、8×声明、EDCOX1×9×语句、带有EDCOX1以外的标签的语句10和EDCOX1、11、TIE块、非文本类型的变量的定义、静态或线程存储持续时间的定义、定义。对不执行初始化的变量的。
    • 参数和返回类型必须是文本类型(即,一般来说,非常简单的类型,通常是标量或聚合)

常量表达式

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

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

    1
    2
    3
    4
    5
    6
    7
    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。例子:好的。

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

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

那么我什么时候才能真正使用constexpr?好的。

  • 像上面的N这样的对象可以用作常量表达式,而不需要声明constexpr。这适用于以下所有对象:好的。

    • const
    • 整数或枚举类型的
    • 在声明时用本身是常量表达式的表达式初始化
      BR/>

    [这是由于§5.19/2:常量表达式不得包含涉及"lvalue to rvalue修改的子表达式,除非[…]a glvalue of integral or enumeration type[…]",感谢Richard Smith更正我之前的声明,对于所有文字类型来说,这是真的。]]好的。

  • 要使函数适合用于常量表达式,必须显式声明constexpr;仅仅满足常量表达式函数的条件是不够的。例子:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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
    }

我什么时候可以/应该同时使用这两种方法,constconstexpr?好的。

在对象声明中。当两个关键字都引用要声明的同一对象时,这是不必要的。constexpr表示const。好的。

1
constexpr const int N = 5;

是一样的好的。

1
constexpr int N = 5;

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

1
2
3
4
5
6
static constexpr int N = 3;

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

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

b.在成员函数声明中。在C++ 11中,EDCOX1〔0〕表示EDCOX1〔3〕,而在C++ 14和C++ 17中则不是这样。C++ 11下声明的成员函数好的。

1
constexpr void f();

需要声明为好的。

1
constexpr void f() const;

在C++ 14下,仍然可以用作EDCOX1×3函数。好的。好啊。


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

constexpr告诉编译器这个表达式会产生编译时常量值,因此它可以用于数组长度、分配给const变量等位置。oli给出的链接有很多很好的例子。

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


概述

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

    考虑:

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

    函数max()只返回一个文本值。但是,由于初始值设定项是一个函数调用,因此mx将进行运行时初始化。因此,不能将其用作常量表达式:

    1
    int arr[mx];  // error:"constant expression required"
  • EDCOX1 4是一个新的C++ 11关键字,它需要你创建宏和硬编码文字。它还保证在某些条件下,对象会进行静态初始化。它控制表达式的计算时间。通过强制对表达式进行编译时评估,constexpr允许您在依赖编译时常量的任何代码中定义对时间关键型应用程序、系统编程、模板至关重要的真正常量表达式。

常量表达式函数

常量表达式函数是声明为constexpr的函数。它的主体必须是非虚拟的,并且只包含一个返回语句,typedef和静态断言除外。其参数和返回值必须具有文本类型。它可以与非常量表达式参数一起使用,但完成后,结果不是常量表达式。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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的对象。它必须用常量表达式初始化,或者用带有常量表达式参数的常量表达式构造函数构造的右值初始化。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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和静态断言。其参数必须具有文本类型。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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关于EDCOX1的有效现代C++的技巧(4):

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

来源:使用CONTXEPR提高C++的安全性、性能和封装性。


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

1
2
3
4
5
6
7
8
9
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。例如:

1
constexpr double square(double x) { return x?x; }

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


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

constconstexpr都意味着它们的值在初始化后不能更改。例如:

1
2
3
4
5
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总是在编译时进行计算。例如:

1
2
3
4
5
6
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数组。

1
2
3
4
int temp=rand(); // temp is generated by the the random generator at runtime.

int array1[10]; // OK.
int array2[temp]; // ERROR.

这意味着:

1
2
3
4
5
6
7
8
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关键字意味着该方法不能更改其成员(非静态)字段的值。例如。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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是一个不同的概念。它将函数(成员或非成员)标记为函数,如果编译时常量作为参数传递,则可以在编译时对其进行计算。例如,你可以写这个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.

顺便说一下,EDCOX1的1个函数是即使通过了非常量参数也可以调用的规则C++函数。但在这种情况下,您将得到非constexpr值。

1
2
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还可以应用于成员函数(方法)、运算符甚至构造函数。例如。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class test2
{
    static constexpr int function(int value)
    {
        return(value+1);
    }

    void f()
    {
        int x[function(10)];


    }
};

更"疯狂"的样本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.


正如@ 0x499 602D2已经指出的那样,EDCOX1〔0〕仅保证在初始化之后不能改变值,因为EDCOX1×4(在C++ 11中引入)保证变量是编译时常数。考虑以下示例(来自learncpp.com):

1
2
3
4
5
6
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


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

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

下面是一个很好的例子:

1
2
3
4
5
6
7
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 time
    constexpr int r = 2^3; // this works!
    // r = 42; // same as const too, it cannot be changed
}

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