【问题标题】:why dynamic initialization occur before static initialization in gcc为什么动态初始化发生在 gcc 中的静态初始化之前
【发布时间】:2020-05-29 05:51:59
【问题描述】:
#include <iostream>
struct NonConstant{
    NonConstant(int v):v_(v){
        std::cout<<"NonConstant\n";
    }
    int v_;
};

struct Constant{
    constexpr Constant(int v):v_(v){
        if(v_==0){
         std::cout<<"Constant\n";
        }
    }
    int v_;
};

NonConstant a = 2; //#1
Constant b = 0;   //#2

int main(){
}

outcome 将是:

NonConstant
Constant

我对这个结果感到困惑,因为,根据标准规则,#1 不是静态初始化,#2 是,因为这些:

一个变量或临时对象的常量初始化器 o 是一个其完整表达式是一个常量表达式的初始化器,除了如果 o 是一个对象,这样的初始化器还可以为 o 调用 constexpr 构造函数及其子对象,即使这些对象属于非文字类类型。
如果具有静态或线程存储持续时间的变量或临时对象由实体的常量初始化程序初始化,则执行常量初始化。如果不执行常量初始化,则将具有静态存储持续时间或线程存储持续时间的变量初始化为零。 零初始化和常量初始化统称为静态初始化;其他所有初始化都是动态初始化。 所有静态初始化都发生在 ([intro.races]) 任何动态初始化之前

NonConstant的构造函数不是由constexpr指定的,NonConstant a = 2;的初始化会为对象a调用一个非constexpr构造函数,因此#1的初始化不是静态初始化,所以是动态初始化。相比之下,Constant b = 0; 的初始化是静态初始化,因为被调用的构造函数是 constexpr 构造函数。并且规则说所有静态初始化都强烈发生在任何动态初始化之前。那么,为什么结果意味着#1 的评估发生在#2 的评估之前?如果我错过了什么,请纠正我。

更新:

在本题后面的cmets中,有人说除了构造函数的类可以是非文字类型外,constexpr构造函数在任何方面都必须是有效的核心常量表达式,即std::cout的调用会使 constexpr 构造函数不是核心常量表达式。不过,我在cppreference找到了另一种解释,那就是:

在(C++14 之前)而不是(C++14 起)静态和线程局部对象的零初始化之后以及所有其他初始化之前执行常量初始化。只有以下变量被常量初始化:

  1. [...]
  2. 由构造函数调用初始化的类类型的静态或线程本地对象,如果构造函数是 constexpr 并且所有构造函数参数(包括隐式转换)都是常量表达式,并且如果构造函数的初始值设定项列表中的初始值设定项并且类成员的大括号或等号初始化器只包含常量表达式

并不是说 constexpr 构造函数必须是核心常量表达式。只要调用的构造函数满足constexpr 限定并且它的参数都必须是常量表达式并且成员初始化器必须是常量表达式。所以,#2 确实是一个常量初始化,因为参数0 是一个常量表达式,并且被指定符constexpr 限定的选定构造函数和成员初始化器遵循expr.const 中提到的这些规则。

【问题讨论】:

标签: c++ c++17 language-lawyer


【解决方案1】:

b 有动态初始化,而不是静态初始化。

正如您对[basic.start.static]/2 的引用已经解释的那样,b 只有在其初始化程序的完整表达式(即Constant(int) 构造函数的执行)是一个常量表达式时才具有静态初始化。

[expr.const]/2,我们读到:

表达式e 是一个核心常量表达式,除非e 的计算遵循抽象机的规则,将计算以下表达式之一:

  • ...

  • 对文字类的 constexpr 构造函数以外的函数的调用、constexpr 函数或对普通析构函数 ([class.dtor]) 的隐式调用 [ 注意:照常应用 —  尾注 ] ;

  • ...

这里“遵循抽象机规则”的构造函数的求值包括构造函数体。由于初始化程序是0,因此该评估将调用std::operator&lt;&lt;(std::ostream&amp;, const char*),而不是constexpr。所以初始化器的完整表达式不是核心常量表达式,也不是常量表达式。

当然,虽然这不是严格的技术定义,但“常量表达式”的全部意义在于定义我们何时保证编译器可以在编译时处理某些事情。并且写入程序的标准输出肯定不会在编译时发生。

cppreference.com 是一个很好的资源,它力求尽可能准确,但它不能替代实际标准的权威性。关于使用类构造函数进行常量初始化的引用对于 C++14 和 C++17 是不正确的。我怀疑它实际上是从 C++11 遗留下来的,其中 constexpr 构造函数的主体根本不允许评估任何函数调用,并且 [expr.const] 类似地描述了使用 constexpr 构造函数的要求就成员初始化器而言的核心常量表达式。

【讨论】:

  • 你的意思是,关于except后面那句话的正确理解是,constexpr构造函数除了构造函数的类是否是字面量类型之外,在其他任何方面都应该满足核心常量表达式的所有要求与否,对吗?
  • 是的,“即使”措辞将例外限制为不是常量表达式的一个原因。
  • 谢谢。在你回答之前,我误读了句子的意思,但是我仍然觉得句子不清楚。即,这句话可能会让读者产生歧义,尤其是exceptconstant expression后面的措辞,会让读者理解为,只要被评估的构造函数是constexpr构造函数,那么这种情况将是例外.
  • 同意,也许有办法更清楚地表达它。
  • For an object, if the full-expression satisfy all requirements for a constant expression in any respects other than literal type, then it will be a static initialization 会更清楚。 :-)
猜你喜欢
  • 2012-01-07
  • 2011-03-30
  • 1970-01-01
  • 2017-08-27
  • 2016-12-30
相关资源
最近更新 更多