constexpr与static const:哪个更好?

94

在函数和类作用域下,用于定义像以下这样的整型编译时常量的语法,哪种语法最好?

static const int kMagic = 64; // (1)
constexpr int kMagic = 64;    // (2)

(1)同样适用于C++98/03编译器,而(2)至少需要C ++11。这两者之间是否还有其他差异?在现代C ++代码中应该偏好哪个,为什么?


编辑

我用Godbolt's CE尝试了这个示例代码:

int main()
{
#define USE_STATIC_CONST
#ifdef USE_STATIC_CONST
  static const int kOk = 0;
  static const int kError = 1;
#else
  constexpr int kOk = 0;
  constexpr int kError = 1;
#endif
  return kOk;
}

对于static const的情况,这是GCC 6.2生成的汇编代码:

main::kOk:
        .zero   4
main::kError:
        .long   1
main:
        push    rbp
        mov     rbp, rsp
        mov     eax, 0
        pop     rbp
        ret

另一方面,对于constexpr,它是:

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 0
        mov     DWORD PTR [rbp-8], 1
        mov     eax, 0
        pop     rbp
        ret

虽然在两种情况下都使用了 -O3,但我得到了相同的(优化后的)汇编代码:

main:
        xor     eax, eax
        ret

编辑 #2

我尝试了这段简单的代码(在Ideone上实时运行)

#include <iostream>
using namespace std;

int main() {
    const int k1 = 10;
    constexpr int k2 = 2*k1;
    cout << k2 << '\n';
    return 0;
}

这表明const int k1编译时计算,因为它用于计算constexpr int k2

然而,对于double似乎有不同的行为。我已经为此创建了一个单独的问题,链接在此


2
我更喜欢使用 constexpr 方法。它是明确强制的。无论如何,另一种选择在链接方面并不相同:如果您使用const(或static,或两者),它会使变量具有 内部 链接。因此,它们在语义上是不同的。 - Nawaz
2
优先选择哪一个取决于你将用它们做什么。正如@Nawaz所说,这两个例子有不同的功能,如果没有提及它们的预期用途,就无法决定哪一个更好。 - Pete Becker
1
你在哪个范围内定义它们? - HolyBlackCat
1
@Mr.C64 当你正在创建一些类,这些类将被用作模板的参数时,这是使用constexpr的好时机:所有成员都是constexpr,而static const无法做到这一点。----这就是我想说的。 - Yves
3
你编辑的GCC汇编代码并不能证明什么。 - Nawaz
显示剩余13条评论
2个回答

78

constexpr 的变量在编译时保证有一个可用值。而 static const 成员或 const 变量可以表示编译时值或运行时值。使用 constexpr 可以更明确地表达你的意图。

另外,在 C++17 中,constexpr 静态数据成员变量也会被内联。这意味着你可以省略 static constexpr 变量的 out of line 定义,但不能省略 static const


如评论区中的要求,这里有一个更详细说明关于函数作用域下的 static const

在函数作用域下的 static const 变量基本相同,但是它具有静态存储期而不是自动存储期。这意味着它在某种程度上等价于将变量声明为全局变量,但只能在函数内部访问。

虽然 static 变量是在第一次调用函数时初始化的,但由于也是 const,编译器会尝试将其值内联并完全优化掉该变量。因此,在函数中,如果对于特定变量的值在编译时已知,则编译器很可能会优化它。

但是,如果在函数作用域下的 static const 的值在编译时未知,则它可能会使你的函数(微小地)变慢,因为它必须在第一次调用函数时在运行时初始化该值,并且每次调用该函数时都必须检查是否已初始化该值。

这就是使用 constexpr 变量的优势所在。如果在编译时未知该变量的值,那么它将导致编译错误,而不是使函数变得更慢。然后,如果你无法确定变量的值,编译器会提示你并让你采取措施。


2
@Mr.C64:关键词 static 与编译时的常数性可计算性没有任何关系。在命名空间级别,它影响连接;在类作用域中,它使成员对所有实例共享;在函数作用域中,它使变量在调用之间持续存在 - Nawaz
@Nawaz:我知道static的作用,但我认为static可以被用作C++编译器优化的提示,将static const变量视为编译时常量。至少在C++11的constexpr出现之前,static const被用作(近似的)编译时常量。 - Mr.C64
1
@Mr.C64:关键是,static const可以是编译时常量,但并不意味着它必须是。 - Nawaz
7
“稍微慢一点”的部分指的不仅是因为函数第一次被调用时需要初始化值,而且还需要在每次调用时运行时确定是否是第一次被调用。这并不是一件简单的事情,请参见https://godbolt.org/g/wBejUj。 - The Vee
@Guillaume Racicot:类的static const int成员初始化为常量表达式,保证作为整数常量表达式,即编译时常量。只要在代码中没有任何地方使用odr,它就不需要单独定义。只要我们谈论的是rvalue上下文,constexpr int常量和static const int常量之间没有区别。(局部范围内的const int对象也适用。)区别纯粹是风格上的。 - AnT stands with Russia
显示剩余11条评论

45
只要我们谈论的是声明标量整数或枚举类型的编译时常量,使用const(类作用域中的static const)或constexpr没有任何区别。
请注意,编译器必须支持使用常量初始化程序声明的static const int对象在常量表达式中,这意味着它们别无选择,只能将这些对象视为编译时常量。此外,只要这些对象保持未使用,它们就不需要定义,这进一步证明了它们不会被用作运行时值。
另外,常量初始化规则防止本地static const int对象被动态初始化,这意味着对于局部声明这样的对象不会有性能损失。此外,整数类型的static对象免受静态初始化排序问题的影响是语言中非常重要的功能。 constexpr是一个概念的扩展和概括,它最初是通过具有常量初始化程序的const在C++中实现的。对于整数类型,constexpr并没有提供任何额外的功能,超过了const已经提供的内容。constexpr只是对初始化程序的"constness"提供了一个提前检查。但是,可以说constexpr是专门为此目的设计的功能,因此在风格上更加合适。

2
感谢您的回答。顺便说一下,关于使用const初始化constexpr时,似乎doubleint有不同的处理方式(但在MSVC中不是这样)。 - Mr.C64
@Mr.C64:是的,没错。我在回答中错误地使用了“标量”类型这个术语。完整的“编译时常量”处理仅适用于整数或枚举类型的const对象。 - AnT stands with Russia
谢谢。更喜欢constexpr的另一个原因。 - Mr.C64
常量初始化规则防止局部的“static const int”对象在动态初始化时被初始化。你的意思是如果初始化器是常量表达式,对吗? - aschepler
这并不完全相同,如果您将一个变量声明为constexpr,则它将在编译时初始化,这将增加编译时间,但会使运行时更快吗? - Vegeta

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