静态数据成员的类内初始化

17
在C++中,static 成员变量不能在类体中进行初始化,但有以下例外:
  • const 整数类型的 static 成员可以被初始化
  • constexpr 字面类型的 static 成员必须被初始化
为什么会存在这些例外呢?
另外,即使一个 const static 数据成员在类体中进行了初始化,这个成员通常应该在类定义外部进行定义。
我从来没有完全理解这一点。这个额外的定义有什么意义呢?
只是试图在这里获得一些直觉。

2
关键点在于变量是否会被odr-used。只要不是,实际上你不需要定义它,因为每当引用变量时粘贴其就足够了。 - Kerrek SB
4
这并不是额外的定义,而是一个定义。在类体内,你有一个声明。 - Pete Becker
1
由于您可能需要从不同的翻译单元(= cpp文件)访问静态数据成员,因此您必须定义一个“位置”,每个翻译单元都引用该位置,即定义静态成员的单个翻译单元。这与(全局)extern变量相同。 - dyp
@DyP:那是错误的。在类模板的情况下,链接器必须已经处理冗余定义。这里没有区别。实际上,编译器为什么不能隐式定义每个全局变量,然后简单地合并它们,因为在许多情况下已经需要这样做了呢?C++标准(像往常一样)只是在这里让用户的生活不必要地艰难。 - Puppy
即使链接器可以这样做,我认为对于外部变量而言情况是一样的。根据标准,你目前必须这样做,我猜这也是由于初始化顺序的原因,这也是有用的。 - dyp
1个回答

9

为什么类定义中可以有初始化器?

关于constconstexpr静态数据成员的两个例外:

[class.static.data]/3

[注意:在这两种情况下,成员可以出现在常量表达式中。——end note]

也就是说,如果有初始化器,你可以在常量表达式中使用它们,例如:

struct s
{
    static std::size_t const len = 10;
    int arr[len];
};
std::size_t const s::len;

如果在类定义中未初始化len,则编译器无法轻易地知道其值,在下一行定义arr的长度。
有人可能会争论是否允许在类定义中对非const、非constexpr静态数据成员进行初始化,但这可能会干扰初始化顺序:
[basic.start.init]/2
引用:
专门为类模板静态数据成员定义的定义具有有序初始化。其他类模板静态数据成员(即隐式或显式实例化的特化)具有无序初始化。 具有静态存储持续时间的其他非本地变量具有有序初始化。
也就是说,定义包括初始值设定项的顺序很重要。 非局部对象的(动态)初始化顺序仅在转换单元内定义,这是必须为非const、非constexpr静态数据成员提供定义的另一个原因。
这个额外定义的意义是什么?
我认为评论中已经回答了这个问题。您可能需要添加ODR,即作为具有外部链接的名称,如果它被ODR使用,则只需在一个转换单元中定义静态数据成员。程序员可以选择此转换单元。

所有错误的原因...代码不可读,被强制分割成不同的文件。 - QT-1
使用C++17,我们现在可以在头文件中定义inline变量。 - dyp
确实。inline很有帮助。谢谢!希望C++20模块能够结束所有这些古老的#include传奇.. - QT-1
@user1656671 我还没有深入研究模块,但我猜它们并不能摆脱目标文件和库文件。因此,链接和初始化顺序问题仍然很重要。 - dyp
我期望模块将使头文件变得多余,从而消除与多重包含相关的所有问题。一旦g++在正式版本中包含它们,我计划开始使用模块。当前的C++远远落后于所有主要语言。在C++中编写非平凡代码需要技巧和奇怪的语法,而其他语言则更易读。初始化顺序应该小心处理,但我认为有一个关于什么是安全的定义。 - QT-1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接