【问题标题】:What is `constinit` in C++20?什么是 C++20 中的`constinit`?
【发布时间】:2020-01-10 16:42:56
【问题描述】:

constinitP1143中提出的C++20中新的keywordspecifier

标准中提供了以下示例:

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

  • 变量可以同时是constconstinit 吗? constexprconstinit 呢?

  • 说明符可以应用于哪些变量?为什么我们不能将其应用于非static、非thread_local 变量?

  • 它有什么性能优势吗?

这个问题的目的是作为关于constinit 的一般问题的参考。

【问题讨论】:

    标签: c++ c++20 constinit


    【解决方案1】:
    • constinit 是什么意思?为什么介绍它?我们应该在哪些情况下使用它?

    使用static storage duration 初始化变量可能会导致两种结果¹:

    1. 变量在编译时初始化(constant-initialization);

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

    情况 (2) 是有问题的,因为它可能导致 static initialization order fiasco,这是与全局对象相关的危险错误的来源。

    constinit 关键字只能应用于具有静态存储持续时间的变量。如果修饰变量在编译时未初始化,则程序格式错误(即无法编译)。

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


    • 它是否使变量不可变?是暗示const 还是constexpr

    没有也没有。

    但是,constexpr 确实暗示了constinit


    • 变量可以同时是constconstinit 吗? constexprconstinit 呢?

    它可以是constconstinit。它不能同时是constexprconstinit。从措辞:

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

    constexpr 不等同于const constinit,因为前者要求持续销毁,而后者则没有。


    • 说明符可以应用于哪些变量?为什么我们不能将其应用于非static、非thread_local 变量?

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


    • 它有什么性能优势吗?

    没有。但是,在编译时初始化变量的附带好处是它在程序执行期间不需要指令来初始化。 constinit 帮助开发人员确保情况确实如此,而无需猜测或检查生成的程序集。


    ¹:见https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables

    【讨论】:

    • "不能同时是constexprconstinit" 那是因为constexpr 有效地暗示了constinit
    • "constexpr 确实暗示了constinit" - 我不会这么说,因为constexpr 可能应用于局部变量,而constinit 不能。
    • "既可以是 const 也可以是 constinit"。这实际上与 constexpr 相同吗?您还可以澄清为什么将 constinit 应用于局部变量没有意义?
    • @pooya13: 1. 不,constexpr 也要求持续销毁。 2.没有意义,因为非static局部变量在编译时无法初始化。
    • “constexpr 要求不断销毁”。你能暗示一下这是什么意思吗?
    【解决方案2】:

    主要问题

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

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

    示例 - 分段错误

    // 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 中引入

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-01
      • 2017-09-16
      • 1970-01-01
      • 2020-03-19
      • 2020-10-28
      • 2020-08-06
      • 2021-12-15
      相关资源
      最近更新 更多