变量中的const和constexpr有什么区别?

464

以下定义有差别吗?

const     double PI = 3.141592653589793;
constexpr double PI = 3.141592653589793;

如果没有的话,在C++11中哪种风格更受欢迎?


4
Superset: https://dev59.com/UWYq5IYBdhLWcg3w5Ui8constexpr和const的区别是什么?const关键字用于声明常量并防止其被修改,而constexpr关键字则允许定义常量表达式。 常量表达式是在编译时求值的表达式,因此它们可以用于需要编译时常量的上下文中,例如数组大小、模板参数等。 在C++11中,constexpr只能用于处理整数类型和浮点类型的字面值,但在C++14中,它已扩展到包括大多数用户自定义类型,只要该类型具有一些特定的属性,例如具有constexpr构造函数。 - Ciro Santilli OurBigBook.com
两者都是编译时常量。但你可以对第一个使用const_cast并写入它。但由于这不影响在编译时发生的“读取”,所以任何编译器都会将其优化掉。 - Bonita Montero
1
这回答了你的问题吗?constexpr和const有什么区别? - Donald Duck
6个回答

524

我相信它们之间是有区别的。让我们给它们重新命名,这样我们就可以更容易地谈论它们:

const     double PI1 = 3.141592653589793;
constexpr double PI2 = 3.141592653589793;

无论是 PI1 还是 PI2 都是常量,这意味着您无法修改它们。然而, PI2 是一个编译时常量。它必须在编译时初始化。 PI1 可以在编译时或运行时初始化。此外, PI2 可以在要求编译时常量的上下文中使用。例如:

constexpr double PI3 = PI1;  // error

但是:

constexpr double PI3 = PI2;  // ok

并且:

static_assert(PI1 == 3.141592653589793, "");  // error
但是:
static_assert(PI2 == 3.141592653589793, "");  // ok

你应该使用哪个?使用满足你需求的那个。你想确保拥有一个编译时常量,可以在需要编译时常量的上下文中使用吗?你想能够用运行时计算得出的值进行初始化吗?等等。


93
你确定吗?因为 const int N = 10; char a[N]; 是可以工作的,而且数组边界必须是编译时常量。 - fredoverflow
13
就我所写的例子来说,我确信它们是可行的(在发布之前对它们进行了测试)。但是我的编译器允许我将 PI1 转换为编译时整数常量以用于数组中,但不能用作非类型整数模板参数。因此, PI1 的编译时可转换性对我来说似乎有点不稳定。 - Howard Hinnant
46
@FredOverflow说:非const数组下标已经“工作”了约十年(例如有一个g++的扩展程序),但这并不意味着它是严格合法的C++语法(尽管一些较近的C或C++标准_使它合法_,我忘记是哪个)。至于编译时常量的差异,模板参数和用作枚举初始化器是const和constexpr之间唯一显著的差异(而且两者都无法适用于双精度型)。 - Damon
22
5.19节中的第4段是一则(非规范性的)注释,它著名地概述了实现可以在编译时和运行时以不同的方式进行浮点算术运算(例如,与精度相关)。因此,1/PI11/PI2可能会产生不同的结果。然而,我认为这个技术细节并不像本答案中的建议那样重要。 - Luc Danton
6
但是对我来说,constexpr double PI3 = PI1; 正确工作了(MSVS2013 CTP)。我做错了什么? - NuPagadi
显示剩余12条评论

105

没有区别,但是当您拥有具有构造函数的类型时,它很重要。

struct S {
    constexpr S(int);
};

const S s0(0);
constexpr S s1(1);

s0是一个常量,但它不能保证在编译时被初始化。 s1被标记为constexpr,所以它是一个常量,并且因为S的构造函数也被标记为constexpr,所以它将在编译时被初始化。

通常,这很重要,当在运行时进行初始化会耗费时间时,你想把这项工作推迟到编译器上,在那里它也很耗时间,但不会减慢编译后程序的执行时间。


5
我同意:我得出的结论是如果对象的编译时计算不可能,那么使用constexpr会导致一种诊断。但不太清楚的是,如果将参数声明为const而不是constexpr,期望一个常数参数的函数是否能够在编译时执行:也就是说,如果我调用foo(s0),是否会在编译时执行constexpr int foo(S) - Matthieu M.
7
我怀疑foo(s0)是否会在编译时执行,但你永远不知道:编译器允许进行这样的优化。当然,无论是gcc 4.7.2还是clang 3.2都不允许我编译constexpr a = foo(s0); - rici

74

constexpr 表示一个在编译时已知且为常量的值。
const 表示一个只是常量但不一定需要在编译时就已知的值。

int sz;
constexpr auto arraySize1 = sz;    // error! sz's value unknown at compilation
std::array<int, sz> data1;         // error! same problem

constexpr auto arraySize2 = 10;    // fine, 10 is a compile-time constant
std::array<int, arraySize2> data2; // fine, arraySize2 is constexpr

请注意const不像constexpr一样提供相同的保证,因为const对象在编译期间无需用已知的值进行初始化。

int sz;
const auto arraySize = sz;       // fine, arraySize is const copy of sz
std::array<int, arraySize> data; // error! arraySize's value unknown at compilation

所有constexpr对象都是const的,但并非所有const对象都是constexpr的。

如果你想让编译器保证一个变量具有可在需要编译时常量的上下文中使用的值,则需要使用constexpr,而不是const。


3
谢谢您的称赞!在实际场景中,我们可能需要使用编译时常量的情况包括:需要在代码中使用不变的值、需要确保值在编译时不会改变、需要提高代码的性能或需要避免在运行时生成大量的临时对象。 - Mayukh Sarkar
1
@MayukhSarkar 只需在Google中搜索 C++ 为什么要使用 constexpr, 例如 https://dev59.com/zW445IYBdhLWcg3wq8FY - underscore_d

27

constexpr符号常量必须在编译时赋值为已知的值。例如:

constexpr int max = 100; 
void use(int n)
{
    constexpr int c1 = max+7; // OK: c1 is 107
    constexpr int c2 = n+7;   // Error: we don’t know the value of c2
    // ...
}

为了处理那些在编译时无法确定初始值,但初始化后永远不会改变的“变量”的情况,C++提供了第二种形式的常量(一个const)。例如:
constexpr int max = 100; 
void use(int n)
{
    constexpr int c1 = max+7; // OK: c1 is 107
    const int c2 = n+7; // OK, but don’t try to change the value of c2
    // ...
    c2 = 7; // error: c2 is a const
}

const常量”非常常见,有两个原因:

  1. C++98没有constexpr,所以人们使用const
  2. 在不是常量表达式(它们的值在编译时不知道)但初始化后不改变值的 “变量” 本身就十分有用。

参考文献: "Programming: Principles and Practice Using C++" by Stroustrup


39
或许你应该提到,你回答中的文本是从Stroustrup的《使用C++编程: 原理与实践》一书中逐字引用的。 - Aky

5

再举一个例子来理解constconstexpr之间的区别。

int main()
{
    int n;
    cin >> n;               

    const int c = n;        // OK: 'c' can also be initialized at run time
    constexpr int e = n;    // Error: 'e' must be initialized at compile time
}

注意: constexpr 通常在编译时进行评估,但除非它们在需要常量表达式的上下文中调用,否则不能保证这样做。

constexpr int add(int a, int b)
{
    return a + b;
};


int main()
{
    int n = add(4, 3);          // may or may not be computed at compile time
    constexpr int m = add(4,3); // must be computed at compile time
}

-1

constexpr -> 用于编译时常量。这基本上是用于运行时优化的。
const -> 用于运行时常量。


目前你的回答不够清晰。请编辑并添加更多细节,以帮助其他人理解它如何回答所提出的问题。你可以在帮助中心找到有关如何撰写好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接