C++20 中的 constinit 是什么?
What is `constinit` in C++20?
constinit
是一个新的 keyword and specifier in C++20 which was proposed in P1143.
标准中提供了以下示例:
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
想到几个问题:
constinit
是什么意思?为什么介绍它?应该在什么情况下使用?
它是否使变量不可变?是表示const
还是constexpr
?
一个变量可以同时是const
和constinit
吗? constexpr
和 constinit
呢?
说明符可以应用于哪些变量?为什么我们不能将其应用于非 static
、非 thread_local
变量?
它有什么性能优势吗?
此问题旨在作为即将出现的有关 constinit
的一般问题的参考。
- What does
constinit
mean? Why was it introduced? In which cases should we use it?
用 static storage duration 初始化变量可能会导致两种结果¹:
变量在编译时初始化(constant-initialization);
变量在控件第一次通过它的声明时被初始化。
情况 (2) 存在问题,因为它可能导致 static initialization order fiasco,这是与全局对象相关的危险错误的来源。
constinit
关键字只能应用于具有静态存储持续时间的变量。如果修饰变量没有在编译时初始化,则程序格式错误(即无法编译)。
使用constinit
确保变量在编译时初始化,并且静态初始化顺序惨败不会发生。
- Does it make a variable immutable? Does it imply
const
or constexpr
?
没有也没有。
但是,constexpr
确实意味着 constinit
。
- Can a variable be both
const
and constinit
? What about constexpr
and constinit
?
可以是const
也可以是constinit
。它不能既是 constexpr
又是 constinit
。来自写法:
At most one of the constexpr
, consteval
, and constinit
keywords shall appear in a decl-specifier-seq.
constexpr
不等同于 const constinit
,因为前者要求 持续破坏 ,而后者则不然。
- To which variables can the specifier be applied? Why cannot we apply it to non-
static
, non-thread_local
variables?
它只能应用于具有静态或线程存储持续时间的变量。将它应用于其他变量没有意义,因为 constinit
是关于静态初始化的。
- Does it have any performance advantages?
没有。但是,在编译时初始化变量的附带好处是它不需要在程序执行期间进行初始化的指令。 constinit
帮助开发人员确保情况确实如此,而无需猜测或检查生成的程序集。
¹:参见 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
解决方法:
constinit 在 C++20 中引入
constinit
是一个新的 keyword and specifier in C++20 which was proposed in P1143.
标准中提供了以下示例:
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
想到几个问题:
constinit
是什么意思?为什么介绍它?应该在什么情况下使用?它是否使变量不可变?是表示
const
还是constexpr
?一个变量可以同时是
const
和constinit
吗?constexpr
和constinit
呢?说明符可以应用于哪些变量?为什么我们不能将其应用于非
static
、非thread_local
变量?它有什么性能优势吗?
此问题旨在作为即将出现的有关 constinit
的一般问题的参考。
- What does
constinit
mean? Why was it introduced? In which cases should we use it?
用 static storage duration 初始化变量可能会导致两种结果¹:
变量在编译时初始化(constant-initialization);
变量在控件第一次通过它的声明时被初始化。
情况 (2) 存在问题,因为它可能导致 static initialization order fiasco,这是与全局对象相关的危险错误的来源。
constinit
关键字只能应用于具有静态存储持续时间的变量。如果修饰变量没有在编译时初始化,则程序格式错误(即无法编译)。
使用constinit
确保变量在编译时初始化,并且静态初始化顺序惨败不会发生。
- Does it make a variable immutable? Does it imply
const
orconstexpr
?
没有也没有。
但是,constexpr
确实意味着 constinit
。
- Can a variable be both
const
andconstinit
? What aboutconstexpr
andconstinit
?
可以是const
也可以是constinit
。它不能既是 constexpr
又是 constinit
。来自写法:
At most one of the
constexpr
,consteval
, andconstinit
keywords shall appear in a decl-specifier-seq.
constexpr
不等同于 const constinit
,因为前者要求 持续破坏 ,而后者则不然。
- To which variables can the specifier be applied? Why cannot we apply it to non-
static
, non-thread_local
variables?
它只能应用于具有静态或线程存储持续时间的变量。将它应用于其他变量没有意义,因为 constinit
是关于静态初始化的。
- Does it have any performance advantages?
没有。但是,在编译时初始化变量的附带好处是它不需要在程序执行期间进行初始化的指令。 constinit
帮助开发人员确保情况确实如此,而无需猜测或检查生成的程序集。
¹:参见 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
解决方法:
constinit 在 C++20 中引入