【问题标题】:Why can't I make in-class initialized `const const std::string` a static member为什么我不能将类内初始化的`const const std::string`设为静态成员
【发布时间】:2013-06-27 19:42:30
【问题描述】:

我有以下工作代码:

#include <string>
#include <iostream>

class A {
public:
  const std::string test = "42";
  //static const std::string test = "42"; // fails
};

int main(void){
  A a;
  std::cout << a.test << '\n';
}

是否有充分的理由无法将测试设为 static const ?我确实理解在 c++11 之前它受到标准的限制。我认为 c++11 引入了类内初始化以使其更友好一些。很长一段时间以来,我也没有这种语义可用于整数类型。

当然,它适用于const std::string A::test = "42";形式的类外初始化

我想,如果你可以使它成为非静态的,那么问题就出在两者之一。在类外范围内初始化它(通常consts 是在对象实例化期间创建的)。但是,如果您要创建一个独立于该类的任何其他成员的对象,我认为这不是问题。第二个是对静态成员有多个定义。例如。如果它被包含在几个.cpp 文件中,然后登陆到多个目标文件中,那么链接器在将这些对象链接在一起(例如,到一个可执行文件中)时会遇到麻烦,因为它们将包含相同符号的副本。依我的理解,这完全等同于 one 在 header 中的类声明下提供类外权限,然后在多个地方包含这个公共 header 的情况。我记得,这会导致链接器错误。

但是,现在处理这个问题的责任转移到了用户/程序员身上。如果一个人想要一个带有static 的库,他们需要提供一个类外定义,将其编译成一个单独的对象文件,然后将所有其他对象链接到这个对象,因此只有一个二进制副本符号的定义。

我阅读了Do we still need to separately define static members, even if they are initialised inside the class definition?Why can't I initialize non-const static member or static array in class? 中的答案。

我还是想知道:

  1. 只是一个标准的东西,还是背后有更深层次的推理?
  2. 这可以通过constexpr 和用户定义来解决吗 文字机制。 clang 和 g++ 都说变量不能有非文字类型。也许我可以做一个。 (也许出于某种原因这也是一个坏主意)
  3. 链接器只包含一个 符号?因为它是static const,所以应该是二进制精确的 不可变的副本。

如果我遗漏或误解了某些内容,请也发表评论。

【问题讨论】:

  • 说到静态,只能在声明的时候初始化const整型和枚举。
  • @juanchopanza 我知道。我认为在 c++11 中它可以被克服。
  • 我认为您在 3. 中指出了问题之一。是的,它是可行的,但它需要这种情况是特殊情况,否则链接器只是不“喜欢”多个定义。
  • 我从一开始就不明白这些限制的基本原理。多重定义的相同符号的问题也适用于模板实例化,因此链接器已经知道如何处理它(通过丢弃除一个之外的所有副本)。
  • @ondav 如果它的const static 真的是“多个”。不只是副本吗?为什么不选一个?我对链接器输入、地址翻译和其他东西不是很熟悉。也许有人知道为什么它这么难或被丢弃的想法。老实说,以前我认为这是类内初始化的问题而不是多个符号,但似乎并非如此。

标签: c++ c++11 in-class-initialization


【解决方案1】:

你的问题有两个部分。标准是怎么说的?为什么会这样?

对于const std::string 类型的静态成员,它需要在类说明符之外定义,并且在其中一个翻译单元中有一个定义。这是单一定义规则的一部分,在 C++ 标准的第 3 条中有规定。

但是为什么呢?

问题是具有静态存储时长的对象在最终程序映像中需要唯一的静态存储,因此它需要从一个特定的翻译单元链接。类说明符在一个翻译单元中没有主目录,它只定义类型(要求在使用它的所有翻译单元中都进行相同的定义)。

常量积分不需要存储的原因是编译器将其用作常量表达式并在使用时内联。它永远不会出现在程序图像中。

然而,像std::string 这样具有静态存储持续时间的复杂类型需要存储,即使它们是const。这是因为它们可能需要动态初始化(在进入 main 之前调用它们的构造函数)。

您可能会争辩说,编译器应该在每个使用它们的翻译单元中存储有关具有静态存储持续时间的对象的信息,然后链接器应该在链接时将这些定义合并到程序映像中的一个对象中。我对为什么没有这样做的猜测是,它需要链接器提供太多智能。

【讨论】:

  • “常量积分不需要存储的原因是它被编译器用作常量表达式并在使用时内联。它永远不会进入程序映像。”除了我可以写需要存储的&amp;const_int_member。我从不引用的静态常量 int 不需要存储。最后,模板需要来自链接器的完全相同的“太多智能”。据我所知,这整个论点在每个细节上都是无稽之谈。 (并不是说你一个人在制作它......)
  • @Nemo: 执行间接 (&amp;const_int_member) 会导致它被 odr-used,因此 9.4.2p4 中提到的要求开始发挥作用,即“成员仍应在命名空间范围,如果它在程序中被 odr-used (3.2) 并且命名空间范围定义不应包含初始化程序。" (即现在需要一个定义) 至于模板,它们有特殊的实例化单元,在翻译阶段 8 中使用特殊规则。
  • 好的,但是一旦您将其设置为“odr-used”,它就会与任何用户定义的类型具有所有相同的问题。所以这个理由仍然不成立;根本没有理由不让任何类型在标头中初始化,即使您仍然需要在命名空间范围内的某个地方进行单个定义。据我所知,当你仔细考虑时,“const int”和“constwhat”之间的这种区别真的没有意义......
  • @Nemo:不,它没有同样的问题。正如我之前所说,可以内联常量整数类型(无论是否使用 odr,以及它们是否具有存储空间),而用户定义类型并非在所有情况下都可以,因为它们需要动态初始化和存储。
  • 因此,设计一个漂亮的用户定义文字很可能没有解决方法。我已经知道,积分是一个非常特殊的例外,只是因为它会内联到汇编调用中,并且不需要存储在最终对象中。因此,您不能取消引用它,而如果它是在类之外定义的,则可以。我认为其他答案甚至有例子。我也知道9.4.2。我只是认为在 c++11 中它们以某种方式使其成为可能。我的问题现在似乎有点多余。也许有人会解释为什么编译器不能执行看似简单的工作,如果不是我接受这个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-07
相关资源
最近更新 更多