协慌网

登录 贡献 社区

`constexpr` 和 `const` 之间的区别

constexprconst什么区别?

  • 什么时候只能使用其中之一?
  • 我什么时候可以同时使用,又该如何选择?

答案

基本含义和语法

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

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

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

当应用于函数时,基本区别是:

  • const仅可用于非静态成员函数,而不能用于一般函数。它保证成员函数不会修改任何非静态数据成员(可变数据成员除外,可变数据成员可以随时进行修改)。

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

    • 函数体必须是非虚拟的并且非常简单:除 typedef 和静态断言之外,仅return语句。对于构造函数,仅允许使用初始化列表,typedef 和静态断言。 (不过,也允许使用= default= delete
    • 从 C ++ 14 开始,规则更加宽松,此后允许在 constexpr 函数中使用: asm声明, goto casedefault之外的标签的语句,try-block,变量的定义非文字类型,静态或线程存储持续时间变量的定义,不执行初始化的变量的定义。
    • 参数和返回类型必须是文字类型(即,通常来说,非常简单的类型,通常是标量或集合)

常数表达式

如上所述, 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 constexpr
       list<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)指向非 const 对象的指针不能是常量表达式,并且(b) &N实际上是指向常量的指针)。

B. 在成员函数声明中。在 C ++ 11 中, constexpr隐含const ,而在 C ++ 14 和 C ++ 17 中则并非如此。在 C ++ 11 下声明为的成员函数为

constexpr void f();

需要声明为

constexpr void f() const;

const函数,请在 C ++ 14 下使用。

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

constexpr告诉编译器该表达式产生一个编译时间常数值,因此可以在诸如数组长度,分配给const变量等地方使用。Oli 提供的链接有很多很好的例子。

基本上,它们总共是 2 个不同的概念,可以(并且应该)一起使用。

概述

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

    考虑:

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

    函数max()仅返回文字值。但是,由于初始化程序是函数调用,因此mx会进行运行时初始化。因此,您不能将其用作常量表达式

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

常数表达函数

常量表达式函数是声明为constexpr的函数。它的主体必须是非虚拟的,并且除了 typedef 和 static 断言之外,只能由单个 return 语句组成。它的参数和返回值必须具有文字类型。可以将其与非常量表达式参数一起使用,但完成后的结果将不是常量表达式。

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

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 ,只是它在使用前需要进行初始化并且其初始化程序必须是常量表达式。因此,常量表达式对象始终可以用作另一个常量表达式的一部分。

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 和 static 断言之外,它的主体必须为空。它的参数必须具有文字类型。

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

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

资料来源: 使用 constexpr 改善 C ++ 的安全性,性能和封装