constexpr 和 const 有什么区别?

新手上路,请多包涵

constexprconst 有什么区别?

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

原文由 MBZ 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1k
2 个回答

基本含义和语法

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

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

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

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

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

  • constexpr 可以与成员函数和非成员函数以及构造函数一起使用。它声明函数适合在 常量表达式 中使用。编译器只会在函数满足特定标准 (7.1.53,4) 时接受它,最重要的是(†) :

    • 函数体必须是非虚拟的并且非常简单:除了 typedefs 和静态断言之外,只允许一个 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.192 节:常量表达式不得包含涉及“左值到右值修改,除非 […] 整数或枚举类型的左值 […]”的子表达式,感谢 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 被声明为地址常量表达式,即本身就是常量表达式的指针。 (通过将地址运算符应用于静态/全局常量表达式来生成地址时,这是可能的。)这里,需要 constexprconstconstexpr 总是指被声明的表达式(这里是 NP ),而 const 是指 int (它声明了一个指向常量的指针)。删除 const 会使表达式非法(因为 (a) 指向非常量对象的指针不能是常量表达式,并且 (b) &N 实际上是指针- 到常数)。

B. 在成员函数声明中。 在 C++11 中, constexpr 意味着 const ,而在 C++14 和 C++17 中则不是这样。在 C++11 下声明为的成员函数

constexpr void f();

需要声明为

constexpr void f() const;

在 C++14 下仍然可以用作 const 函数。

原文由 jogojapan 发布,翻译遵循 CC BY-SA 4.0 许可协议

const 和 constexpr 关键字概述

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

 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;
 };

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

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

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

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

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

 constexpr const int* p;

构造函数

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

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

 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 函数,函数的返回值类型和函数参数的类型必须属于称为“文字类型”的类型类别。

constexpr 函数是隐式内联函数。

重要的一点:

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

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

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

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

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

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

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

奖金

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

如果我们使用 static 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
 };

原文由 east1000 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题