constexpr和const之间有什么区别?

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


当前回答

正如@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

其他回答

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

但是,对于constexpr来说,它有点不同。

对于constexpr,您可以给出一个表达式,该表达式可以在程序编译期间计算。

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

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

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

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

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

当用文字或表达式初始化时,名称空间/文件范围中的constexpr和const是相同的;但对于函数,const可以由任何函数初始化,但由非constexpr初始化的constexpr(未标记constexpr或非constexpr表达式的函数)将生成编译器错误。constexpr和const都是变量的隐式内部链接(实际上,如果编译-O1或更强,则它们无法生存到链接阶段,并且static不会强制编译器在-O1或更高时发出const或constexpr的内部(本地)链接器符号;它唯一做到这一点的时候是获取变量的地址。const和constexpr将是内部符号,除非用extern表示,即extern constexpr/const int i=3;需要使用)。在函数上,constexpr使函数永远不会到达链接阶段(无论是定义中的extern或inline,还是-O0或-Ofast),而const永远不会到达,static和inline只对-O1和更高版本有此影响。当const/constexpr变量由constexpr函数初始化时,加载总是使用任何优化标志进行优化,但如果函数仅为静态或内联,或者变量不是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    rbp
        mov     rbp, rsp
        mov     esi, 100 //substituted in as an immediate
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     eax, 0
        pop     rbp
        ret

__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 error
int main () {
  std::cout << val;
}

编译到

multiply(int, int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     eax, DWORD PTR [rbp-4]
        imul    eax, DWORD PTR [rbp-8]
        pop     rbp
        ret

main:
        push    rbp
        mov     rbp, rsp
        mov     eax, DWORD PTR val[rip]
        mov     esi, eax
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     eax, 0
        pop     rbp
        ret

__static_initialization_and_destruction_0(int, int):
        . 
        . 
        . 
        mov     esi, 10
        mov     edi, 10
        call    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的行为相同,如果由非constexpr函数初始化,则会产生错误;该值也立即代入。

最后,它的主要目的类似于C的内联函数,但只有当该函数用于初始化文件范围变量时,它才有效(这些函数不能在C上执行,但可以在C++上执行,因为它允许动态初始化文件范围变元),除非该函数也不能将全局/局部符号导出到链接器,即使使用external/static,您可以在C上使用内联;块范围变量赋值函数可以简单地使用-O1优化进行内联,而无需在C和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

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

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

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.

const和constexpr之间的主要区别是它们的初始化值已知(求值)的时间。虽然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.

因此,常量变量可以定义编译时常量(如size1)和运行时常量(例如size2),前者可以用于指定数组大小,后者只能在运行时知道,不能用于定义数组大小。另一方面,constexpr总是定义可以指定数组大小的编译时常数。

const和constexpr也可以应用于函数。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++函数,即使传递了非常量参数,也可以调用这些函数。但在这种情况下,您得到的是非常量表达式值。

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.