constexpr和const之间有什么区别?

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


当前回答

首先,两者都是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。

const和constexpr关键字概述

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

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

例如,我们可以在switch中使用case语句。

constexpr可以与数组一起使用。

constexpr不是类型。

constexpr关键字可以与auto关键字一起使用。

constexpr auto x = 10;

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

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

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

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

初始化是必需的。它只能用于阅读目的。无法更改。到目前为止,“const”和“constexpr”关键字之间没有区别。

注意:我们可以在同一声明中使用constexpr和const。

constexpr const int* p;

Constexpr函数

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

注意:在函数调用中发送给函数的参数变量的参数,如果有多个参数,则发送给所有参数变量,如果是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};

为了使函数成为constexpr函数,函数的返回值类型和函数参数的类型必须在名为“literal type”的类型类别中。

constexpr函数是隐式内联函数。

重要一点:

无需使用常量表达式调用constexpr函数。它不是必需的。如果发生这种情况,计算将不会在编译时完成。它将被视为正常的函数调用。因此,当需要常量表达式时,我们将无法再使用此表达式。

constexpr函数所需的条件如下所示:;

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

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

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

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

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

奖金

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

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

class Myclass  {
         const static int sx = 15;         // OK
         constexpr static int sy = 15;     // OK
         const static double sd = 1.5;     // ERROR
         constexpr static double sd = 1.5; // OK
 };

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

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

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

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

概述

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提高安全性、性能和封装。