在不同翻译单元中具有静态存储持续时间的依赖非局部常量浮点变量的常量初始化
Constant initialization of dependent non-local constant float variables w/ static storage duration in different translation units
我想知道当两个具有静态存储持续时间的常量非局部浮点变量之间存在依赖关系时,我是否可以依赖常量初始化 - 其中一个依赖于(初始化为 [值])另一个,而对于后者,执行常量初始化。我正在寻找提供和解释标准相关部分的答案,特别是 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),引用为 绑定到指定具有静态存储持续时间的对象的 lvalue 或临时(见12.2);
如果初始化了具有静态或线程存储持续时间的对象 通过构造函数调用,并且如果初始化完整表达式为 对象的常量初始值设定项;
如果具有静态或线程存储持续时间的对象不是 由构造函数调用初始化,如果对象是 值初始化或在其中显示的每个完整表达式 初始值设定项是一个常量表达式。
在解释从最后一个项目符号中,如果Bar::kValue
是常量表达式,则Foo::kValue
通过常量初始化进行初始化。我怀疑我可以在 [expr.const] 中找到这是否属实的答案,但在这里我被困住了。
嗯...我不会信任该代码,因为我害怕静态初始化顺序惨败。AFAIK,不同编译单元之间的静态初始化顺序是不确定的。这意味着即使是测试也不会让我相信一切都会好起来的。
在没有进入血腥细节的情况下,我无法在标准中找到任何可以确保在bar.cpp
Bar::kValue
之前初始化foo.cpp
Foo::kValue
的内容。如果顺序错误,则Foo::kValue
的值将无法确定。
const float
不符合常量表达式的要求
(这个答案是基于@Oktalist的评论,因为他没有做出自己的回答)
在以下方面:
// foo.h struct Foo { static const float kValue; }; // foo.cpp const float Foo::kValue = 1.5F;
Foo::kValue
确实是通过常量初始化由常量表达式初始化的,但Foo::kValue
本身不是常量表达式,因为它既不是积分、枚举、constexpr 也不是临时的。[expr.const]/2 声明 [强调我的]:
一个条件表达式是一个核心常量表达式,除非它涉及以下作为潜在计算的子表达式之一([basic.def.odr]), 但逻辑 AND 的子表达式 ([expr.log.and]), 逻辑或 ([expr.log.or]), 和有条件的 ([expr.cond]) 未计算的操作不被视为 [ 注意:一个 重载运算符调用函数。— 尾注 ]:
。
(2.9):左值到右值的转换([conv.lval])除非它适用于
- 整数或枚举类型的 GL值,引用具有前面初始化的非易失性 const 对象,初始化 使用常量表达式,或
- 文字类型的 glvalue 引用使用 constexpr 定义的非易失性对象,或引用此类对象的子对象 对象,或
- 文本类型的 gl值,它引用生存期尚未结束的非易失性临时对象,使用常量初始化 表达;
由于(2.9)的任何子句都不适用于此处,因此Foo::kValue
不是一个常量表达式。从 [basic.start.init]/2(如问题中较早的标准版本中引用)可以看出,Bar::kValue
不是通过常量初始化进行初始化,而是作为动态初始化的一部分。
具有静态存储持续时间([basic.stc.static])或线程存储持续时间([basic.stc.thread])的变量应在任何其他初始化发生之前进行零初始化([dcl.init]):
执行常量初始化:
- 。
- 如果具有静态或线程存储持续时间的对象未由构造函数调用初始化,并且如果每个完整表达式 出现在其初始值设定项中的是一个常量表达式。
关于"静态初始化订单惨败"的说明
请注意,此特定示例不会导致静态初始化顺序惨败的风险,因为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
可以在Foo::kValue
之前初始化(使用Foo::kValue
的"值")。
- #定义c-预处理器常量..我做错了什么
- 如何从C++中的依赖类型中获得它所依赖的类型
- 用C++中的一个变量定义一个常量
- 什么时候在C++中返回常量引用是个好主意
- 代理对象的常量正确性
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- 通过多个头文件使用常量变量
- 在cuda线程之间共享大量常量数据
- 不能在初始值设定项列表中将非常量表达式从类型 'int' 缩小到'unsigned long long'
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 是默认情况下分配给char数组常量的值
- 私有类型的静态常量成员
- 类似枚举的计算常量
- 递归模板化函数不能分配给具有常量限定类型"const tt &"的变量"state"
- 为什么我可以通过引用修改常量返回
- 将--whole archive链接器选项与CMake和具有其他库依赖项的库一起使用
- 初始化依赖于子类的继承类的常量类成员
- 在不同翻译单元中具有静态存储持续时间的依赖非局部常量浮点变量的常量初始化
- 设计:常量和非常量访问器相互依赖
- 为什么标准不允许在模板形参列表中初始化依赖于常量的类型