不同翻译单元中具有静态存储持续时间的依赖非局部常量浮点变量的常量初始化

Constant initialization of dependent non-local constant float variables w/ static storage duration in different translation units

我想知道当两个不同翻译单元中具有静态存储持续时间的两个常量非局部浮点变量之间存在依赖关系时,我是否可以依赖常量初始化 - 其中一个依赖于(初始化为 [值of]) 另一个,而对于后者,执行常量初始化。我正在寻找提供和解释标准相关部分的答案,尤其是 C++11 标准。

// Note: the non-use of constexpr is intended (C++03 compatibility)

// foo.h
struct Foo {
  static const float kValue;
};

// foo.cpp
const float Foo::kValue = 1.5F;

// bar.h
struct Bar {
  static const float kValue;
};

// bar.cpp
#include "foo.h"
const float Bar::kValue = Foo::kValue;  // Constant initialization?

// main.cpp
#include "bar.h"
#include <iostream>

int main() { std::cout << Bar::kValue; }

我自己的细节/调查

[basic.start.init]/1 表示 [强调 我的]:

Constant initializationis performed:

  • if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (5.19) and the reference is bound to an lvalue designating an object with static storage duration or to a temporary (see 12.2);

  • if an object with static or thread storage duration is initialized by a constructor call, and if the initialization full-expression is a constant initializer for the object;

  • if an object with static or thread storage duration is not initialized by a constructor call and if either the object is value-initialized or every full-expression that appears in its initializer is a constant expression.

从最后一个项目符号中解释,如果 Foo::kValue 是常量表达式,则 Bar::kValue 通过常量初始化的方式进行初始化。我想我可以在 [expr.const] 中找到这是否属实的答案,但我被卡住了。

嗯...我不会相信那个代码,因为我会害怕static initialization order fiasco。 AFAIK,不同编译单元之间静态初始化的顺序是不确定的。这意味着即使测试也不会让我相信一切都会好起来。

在不深入细节的情况下,我无法在标准中找到任何东西来确保 foo.cpp 中的 Foo::kValuebar.cpp 中的 Bar::kValue 之前被初始化。而如果顺序错了,Foo::kValue中的值将只是不确定的。

const float 不满足成为 常量表达式的要求

(这个答案是基于,因为他没有自己做答案)

在下面:

// foo.h
struct Foo {
  static const float kValue;
};

// foo.cpp
const float Foo::kValue = 1.5F;

Foo::kValue确实是由常量表达式通过常量初始化初始化的,但是Foo::kValue是本身不是常量表达式,因为它既不是整数、枚举、constexpr,也不是临时的。 [expr.const]/2 声明 [强调 我的]:

A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression ([basic.def.odr]), but subexpressions of logical AND ([expr.log.and]), logical OR ([expr.log.or]), and conditional ([expr.cond]) operations that are not evaluated are not considered [ Note: An overloaded operator invokes a function. — end note ]:

...

(2.9): an lvalue-to-rvalue conversion ([conv.lval]) unless it is applied to

  • a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
  • a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or
  • a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression;

由于 sub-clauses 的 none for (2.9) 适用于此,因此 Foo::kValue 不是常量表达式。从 [basic.start.init]/2(如问题中较早的标准版本中引用的那样)可以看出,Bar::kValue 不是通过 常量初始化 初始化的,而是作为 动态初始化.

Variables with static storage duration ([basic.stc.static]) or thread storage duration ([basic.stc.thread]) shall be zero-initialized ([dcl.init]) before any other initialization takes place [emphasis mine]:

Constant initialization is performed:

  • ...
  • if an object with static or thread storage duration is not initialized by a constructor call and if every full-expression that appears in its initializer is a constant expression.

关于“static initialization order fiasco”的注释

请注意,此特定示例不会导致静态初始化顺序失败的风险,因为 Foo::kValue 被初始化为常量初始化,而 Bar::kValue 被初始化为动态初始化的一部分,前者保证在动态初始化开始前完成。

如果前者也作为动态初始化的一部分进行初始化,则两者的初始化相对于彼此(以及所有其他动态初始化)的顺序将不确定。

然而,永远不要依赖这个特定示例具有 well-defined 初始化顺序的事实,因为细微的更改会使这一事实无效:

// foo.h
struct Foo {
  static const float kDummyValue;
  static const float kValue;
};

// foo.cpp
const float Foo::kDummyValue = 1.5F;    // Constant initialization
const float Foo::kValue = kDummyValue;  // (!) Dynamic initialization

// bar.h
struct Bar {
  static const float kValue;
};

// bar.cpp
#include "foo.h"
const float Bar::kValue = Foo::kValue;  // (!) Dynamic initialization

// main.cpp
#include "bar.h"
#include <iostream>

int main() { std::cout << Bar::kValue; }

正如在这个修饰符示例中,Foo::kValueBar::kValue 的初始化相对于彼此的顺序是不确定的,这意味着 Bar::kValue 可以被初始化(使用 "value" of Foo::kValue) before Foo::kValue is.