What is `constinit` in C++20?

constinit is a new keyword and specifier in C++20 which was proposed in P1143.

The following example is provided in the standard:

const char * g() { return "dynamic initialization"; }
constexpr const char * f(bool p) { return p ? "constant initializer" : g(); }
constinit const char * c = f(true);     // OK
constinit const char * d = f(false);    // ill-formed

A few questions come to mind:

  • What does constinit mean? Why was it introduced? In which cases should we use it?

  • Does it make a variable immutable? Does it imply const or constexpr?

  • Can a variable be both const and constinit? What about constexpr and constinit?

  • To which variables can the specifier be applied? Why cannot we apply it to non-static, non-thread_local variables?

  • Does it have any performance advantages?

This question is intended to be used as a reference for upcoming questions about constinit in general.

10280 次浏览
  • constinit是什么意思? 为什么引入它? 在什么情况下我们应该使用它?

< em > 静态储存期 初始化一个变量可能会导致两种结果1:

  1. 变量在编译时初始化(常量初始化) ;

  2. 变量在控件首次通过其声明时初始化。

Case (2)是有问题的,因为它可能导致 静态初始化顺序失败,这是与全局对象相关的危险 bug 的来源。

constinit关键字只能应用于具有 静态存储时间静态存储时间的变量。如果修饰变量没有在编译时初始化,程序就是格式不正确的(即不编译)。

使用 constinit可以确保在编译时初始化变量,并且不会发生 静态初始化顺序失败


  • 它是否使一个变量不变? 它是否意味着 constconstexpr

不是,也不是。

然而,constexpr确实意味着 constinit


  • 一个变量能同时是 constconstinit吗? 那 constexprconstinit呢?

它可以是 constconstinit,但不能同时是 constexprconstinit。从措辞来看:

constexprconstevalconstinit关键字中最多只能有一个出现在 decl-specfier-seq 中。

constexpr不等同于 const constinit,因为前者要求 持续的破坏,而后者则不是。


  • 说明符可以应用到哪些变量? 为什么我们不能应用到非 static,非 thread_local变量?

它只能应用于具有静态或线程存储持续时间的变量。将它应用于其他变量是没有意义的,因为 constinit完全是关于静态初始化的。


  • 它有什么性能优势吗?

没有。但是,在编译时初始化变量的附带好处是,在程序执行期间不需要指令进行初始化。constinit可以帮助开发人员确保这一点,而无需猜测或检查生成的程序集。


1: 请参阅 https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables

主要问题 :

只有当控件通过其声明或定义时,对象才被认为是初始化的,否则(即控件跳转到声明或定义该对象的源文件中定义的函数中,根本看不到该对象)对这个未初始化对象的任何访问都是未定义行为的。

此外,定义为多个翻译单元的静态持续时间对象的初始化顺序也没有定义。在代码中没有办法请求编译器在另一个对象之前或之后初始化静态对象,因为一个对象依赖于另一个对象。其实你不能这样做。由编译器决定应该首先初始化哪个对象; 尤其是这实际上取决于编译每个源文件的顺序。

例子 -内存区段错误

// main.cpp
#include "src1.h"
A a{ 10 };      // declaring an object of class A with static duration.
int main() {}


// src1.cpp
#include "src1.h"
#include "src2.h"
B b{ 20 };      // declaring an object of class B with static duration.
A::A(int x): m_x(x) { b.f(); }


//src2.cpp
#include "src2.h"
int B::f() { return m_x; }
B::B(int x):  m_x(x) { }


//src1.h
struct A {
private: int m_x;
public: A(int);
};


//src2.h
struct B {
private: int m_x;
public: B(int); int f();
};


g++ main.cpp src1.cpp src2.cpp // OK: main.cpp should be compiled first
g++ main.cpp src2.cpp src1.cpp // OK: main.cpp should be compiled first
g++ any_other_order // sigfault

解决办法:

在 C + + 20中引入了 Constinit