我想知道当两个具有不变存储持续时间的两个常量非局部浮点变量在两个不同的转换单元中存在依赖关系时,是否可以依赖常量初始化-其中一个依赖于(初始化为[value 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; }
Bar::kValue
是否通过常量初始化进行了初始化? (依次回答是通过静态还是动态初始化来初始化)[basic.start.init] / 1状态[强调我的]:
执行恒定初始化:
如果每个完整表达式(包括隐式转换) 出现在带有静态或线程的引用的初始化程序中 存储持续时间是一个常数表达式(5.19),参考是 绑定到指定静态存储持续时间的对象的左值 或临时(请参见12.2);
如果具有静态或线程存储持续时间的对象已初始化 通过构造函数调用,并且如果初始化full-expression是 对象的常量初始值设定项;
如果具有静态或线程存储持续时间的对象不是 通过构造函数调用初始化,如果对象是 值初始化或出现在其值中的每个完整表达式 初始化程序是一个常量表达式。
根据最后一个项目符号,如果Bar::kValue
是一个常量表达式,则说明Foo::kValue
是通过常量初始化进行初始化的。我怀疑我可以在[expr.const]中找到关于这是否正确的答案,但在这里我被困住了。
答案 0 :(得分:1)
嗯...我不信任该代码,因为我担心使用static initialization order fiasco。 AFAIK,不同编译单元之间的静态初始化顺序不确定。这意味着即使进行测试也无法使我充满信心,一切都会好起来的。
没有输入任何细节,我无法在标准中找到任何东西来确保Foo::kValue
中的foo.cpp
在Bar::kValue
中的bar.cpp
之前被初始化。而且,如果顺序错误,Foo::kValue
中的值将无法确定。
答案 1 :(得分:1)
const float
不符合成为常量表达式 (此答案基于@Oktalist's comment,因为他不愿自己回答)
以下:
// 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 是核心常量表达式,除非它涉及以下之一作为可能评估的子表达式 ([basic.def.odr]), 但是逻辑AND的子表达式 ([expr.log.and]), 逻辑或 ([expr.log.or]), 和有条件的 ([expr.cond]) 未评估的操作将不被考虑[注意: 重载的运算符调用一个函数。 —尾注]:
...
(2.9):左值到右值转换([conv.lval]) 除非将其应用于
- 整数或枚举类型的glvalue,它引用具有先前初始化且已初始化的非易失性const对象 带有常量表达式,或者
- 文字类型的glvalue,它引用用constexpr定义的非易失性对象,或引用此类的子对象 对象,或
- 文字类型的glvalue,它引用一个生命周期尚未结束的非易失性临时对象,并使用常量对其进行了初始化 表达;
由于(2.9)的所有子条款均不适用于此处,因此Foo::kValue
不是常量表达式。从[basic.start.init]/2(在问题的早期标准版本中引用)可以得出,Bar::kValue
不是通过恒定初始化进行初始化的,而是作为 dynamic的一部分进行初始化的初始化。
具有静态存储持续时间([basic.stc.static])或线程存储持续时间([basic.stc.thread])的变量应在进行任何其他初始化之前进行零初始化([dcl.init])[强调< / strong>我的]:
恒定初始化被执行:
- ...
- 如果具有静态或线程存储持续时间的对象未通过构造函数调用初始化,并且如果每个完整表达式 在其初始值设定项中出现的是一个常量表达式。
请注意,此特定示例不会带来静态初始化顺序失败的风险,因为Foo::kValue
被初始化为常量初始化的方式,而Bar::kValue
被初始化为动态初始化的一部分,并且前者可以保证在动态初始化开始之前完成。
如果前者也可以作为动态初始化的一部分进行初始化,则两者的初始化将相对于彼此不确定地排序(以及所有其他动态初始化)。
但是,永远不要依赖这个特定示例具有明确定义的初始化顺序的事实,因为细微的更改会使该事实无效:
// 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::kValue
和Bar::kValue
的初始化相对于彼此不确定地排序,这意味着Bar::kValue
可以被初始化(“ {”的“值” 1}})在Foo::kValue
之前。