【问题标题】:"surprising" constant initialization because of definition order由于定义顺序,“令人惊讶”的常量初始化
【发布时间】:2011-11-29 08:52:43
【问题描述】:

阅读slides about constexpr 时,介绍是关于“令人惊讶的使用 consts 进行动态初始化”。例子是

struct S {
    static const int c;
};
const int d = 10 * S::c;
const int S::c = 5;

唉,音轨不见了,笔记也不见了,所以我只能猜测这里的意思。

d 被“令人惊讶地”动态初始化是否正确,因为 S::c 是在 dd 之前定义的?声明 S::c 的em> 在d 之前可能还不够,编译器需要完整的定义,对吧?

也就是说,我怀疑在以下示例中 d 会被静态初始化吗?

struct S {
    static const int c;
};
const int S::c = 5;
const int d = 10 * S::c;  // now _after_ defn of S::c

为了获得蛋糕,在 C++11 中,constexpr 必须是什么才能进行完全静态初始化? S::cd 或两者兼而有之?

【问题讨论】:

  • 静态成员可以在源文件的任何地方声明。
  • 我怀疑d 在幻灯片中展示的情况下是0,好像我正确地记得静态内存在设置为预期值之前是0初始化的。
  • @vivek declaration 的位置是固定的,但初始化的位置不是。虽然我也觉得这对静态常量很重要,但我们必须记住 C++ 不是 Haskell。
  • @vivik:当然可以。但我认为你的意思是“定义”,是吗?即便如此,这与我关于 staticdynamic 初始化的问题无关......
  • @leftaroundabout: ...但是有了constexpr,它几乎变成了它;-)

标签: c++ initialization constants c++11 constexpr


【解决方案1】:

在第一个例子中,d 没有被常量表达式初始化,因为S::c 不是

具有先前初始化的非易失性 const 对象, 用常量表达式初始化

(参见 C++11 [expr.const]p2,关于左值到右值转换的项目符号),因为 S::c 的初始化不会先于 d 的初始化。所以S::c会使用静态初始化(因为它是用常量表达式初始化的),而d可以使用动态初始化。

由于静态初始化先于动态初始化,d 将通过其动态初始化程序初始化为 50。允许编译器将d 的动态初始化转换为静态初始化,但如果这样做,它必须产生d 的值,如果每个可以使用动态初始化的变量实际上都使用了动态初始化初始化。在这种情况下,d 被初始化为50。有关这方面的更多信息,请参阅 C++11 [basic.start.init]p2。

没有办法在第一个例子中加上constexpr来保证d使用静态初始化;为此,您必须重新排序初始化。但是,添加 constexpr 将为第一个示例生成诊断信息,这至少可以让您确保使用动态初始化(您会遇到静态初始化或编译错误)。

您可以更新第二种情况以确保使用静态初始化如下:

struct S {
    static const int c; // do not use constexpr here
};
constexpr int S::c = 5;
constexpr int d = 10 * S::c;

在不是定义的变量声明上使用constexpr 或在不包含初始化程序的变量声明上使用它是不正确的,因此必须使用const,而不是constexprstruct S 的定义内。此规则有一个例外,即在定义文字、非整数类型的 static constexpr 数据成员时,使用类中指定的初始化程序:

struct T { int n; };
struct U {
    static constexpr T t = { 4 };
};
constexpr T U::t;

在这种情况下,constexpr 必须在类的定义中使用,以便允许提供初始化程序,constexpr 必须在静态数据成员的定义中使用,以便允许它在常量表达式中的使用。

【讨论】:

  • 没错。我想了解新constexpr 背后的原因/原因。因此,我想确保在这里找到constexpr 的充分理由。你证实了我的理解。
【解决方案2】:

我认为 3.6.2 中确定静态初始化何时发生的规则不包括d 的初始化,因此动态初始化。另一方面,S::c 确实是静态初始化的(因为5 是一个常量表达式)。由于所有静态初始化都发生在动态初始化之前,因此您会得到预期的结果。

要使d 符合静态初始化条件,必须使用常量表达式对其进行初始化。这反过来又迫使您编写 S::c 内联:

struct S { static constexpr int c = 5; };

const int d = S::c; // statically initialized

请注意,该标准允许将动态初始化替换为静态初始化,这就是为什么重新排序原始示例中的两行会导致两种不同类型的初始化。正如 TonyK 指出的那样,您可以在静态情况下使用 array[d],但不能在动态情况下使用,因此您可以检查正在发生的情况。使用constexpr 方法,可以保证进行静态初始化,并且不必依赖可选的编译器行为。

【讨论】:

  • "注意标准允许用静态初始化代替动态初始化" 能否给出相关的标准文本?
  • 允许实现将具有静态存储持续时间的非局部变量的初始化作为静态初始化,即使这种初始化不需要静态完成,前提是 — 的动态版本初始化不会在其初始化之前更改命名空间范围内的任何其他对象的值,并且 - 如果不需要初始化所有变量,则初始化的静态版本在初始化变量中产生的值与动态初始化产生的值相同静态地被动态地初始化。
  • "这样您就可以检查发生了什么。"不,您不能。
  • @curiousguy:有什么问题?如果 d 可以静态初始化,则声明 char a[d]; 将编译,这在两种情况之一中有效,但在另一种情况下无效,即使根据标准,永远不能保证 d 是静态初始化的。
  • 所以你不是说d 是一个常量表达式iff d 恰好是静态初始化的吗?你能举一个例子,d 不能保证被静态初始化,但int a[d]; 是合法的?
【解决方案3】:

对于静态初始化,粗略地说,需要一个常量表达式初始化器。

要成为一个常量表达式,粗略地说,一个变量需要是const 类型,并且前面有一个常量表达式的初始化。

在第一个示例中,d 的初始化器不是常量表达式,因为 S::c 不是常量表达式(它没有前面的初始化)。因此,d 不是静态初始化的。

在第二个示例中,d 的初始化程序是一个常量表达式,一切正常。

我正在简化事情。在完整的正式标准中,这将是大约九倍。


对于constexpr 说明符,没有对象必须 声明为constexpr。这只是一个额外的错误检查。 (这是关于 constexprobjects,而不是 constexprfunctions)。

可以在第二个变体中声明S::cconstexpr,如果你想要一些额外的错误保护(也许5明天会开始改变它的值?)将constexpr添加到第一个变体不能可能有帮助。

【讨论】:

  • 作为附加的“错误检查”?检查什么?您的意思可能是“检查是否可以进行动态初始化”,对吗?
  • @towi:不,我的意思是“检查这是否确实是一个常量表达式,正如constexpr 的助记符所建议的那样”。
【解决方案4】:

你可以通过尝试声明一个数组来判断一个常量是静态初始化的还是动态初始化的:

struct S {
    static const int c;
};
const int d = 10 * S::c; // (1)
const int S::c = 5;      // (2)

static char array[d];

此代码在 g++ 版本 4.7.0 中失败,因为 d 是动态初始化的。如果交换 (1) 和 (2),它会编译,因为现在 d 是静态初始化的。但是我找不到其他方法来修复它,使用constexpr

【讨论】:

  • 嗯,我认为constexpr 的更改较晚。首先,定义只能依赖于同一文件中以前的定义。我虽然在这个过程的后期已经改变为更一般的方法,但我不确定。也许 gcc 还没有学会这个?
  • 标准 (3.6.2) 明确允许将动态初始化替换为静态初始化。甚至还有一个例子,这个替换产生了未指定的行为。
  • @TonyK:array[d] 诊断实用程序的问题当然在于定义大常量(例如构建时间戳......)
  • @Matthieu:那就直接说static char array[d & 15] ;吧。
  • static_assert( sizeof(int[d]), "" ); 有效且不涉及实际对象。
猜你喜欢
  • 2012-03-25
  • 2019-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-09
  • 2012-08-11
  • 2011-04-29
  • 1970-01-01
相关资源
最近更新 更多