声明普通类和类模板的静态数据成员

Declaring static data members of normal class and class template

本文关键字:静态 数据成员 声明      更新时间:2023-10-16

我读到在源文件中定义静态数据成员的原因是因为如果它们在头文件中并且多个源文件包含头文件-定义将多次输出。我可以理解为什么这对静态const数据成员来说是个问题,但为什么这对静态数据成员来说是个问题呢?

我不太确定我完全理解为什么有一个问题,如果定义是写在头文件…

变量的多重定义问题是由于语言定义的两个主要缺陷造成的。

如下所示,您可以轻松地绕过

。没有技术原因可以解释为什么没有直接支持。这与该功能没有足够高的需求有关,因此委员会的人选择将其作为优先级。

首先,为什么多个定义通常是一个问题。由于c++缺乏对单独编译模块的支持(缺陷#1),程序员必须通过使用文本预处理等来模拟该特性。然后很容易不经意地引入两个或多个相同名称的定义,这很可能是错误的。

对于函数,这可以通过inline关键字和属性来解决。独立函数只能显式地为inline,而成员函数可以通过在类定义中定义而隐式地为inline。无论哪种方式,如果一个函数是inline,那么它可以在多个翻译单元中定义,并且它必须在使用它的每个翻译单元中定义,并且这些定义必须是等价的。

这个解决方案主要允许在头文件中定义类。

不需要这样的语言特性来支持头文件中定义的数据和变量,所以它不存在:你不能有inline变量。这是语言缺陷#2。

但是,您可以通过类模板的static数据成员的特殊豁免来获得inline变量的效果。豁免的原因是类模板通常必须在头文件中完全定义(除非模板仅在翻译单元中内部使用),因此对于能够具有static数据成员的类模板,有必要从一般规则中豁免,或者提供一些特殊支持。委员会选择了规则豁免路线。
template< class Dummy >
struct math_
{
    static double const pi;
};
template< class Dummy >
double const math_<Dummy>::pi = 3.14;
typedef math_<void> math;

以上被称为模板化的const技巧。据我所知,是我在[comp.lang.c++] Usenet组中介绍了它,所以我不能把功劳归功于别人。我也在SO上发过几次。

无论如何,这意味着每个c++编译器和链接器内部都支持并且必须支持inline数据所需的机制,而语言却没有这个特性。

然而,在第三方面,c++ 11有constexpr,你可以把上面的代码写成
struct math
{
    static double constexpr pi = 3.14;
};

好吧,有一个区别,你不能取c++ 11 math::pi的地址,但这是一个非常小的限制。

我认为你混淆了两件事:static数据成员和static全局变量市场。

后者具有内部链接,这意味着如果您将它们的定义放在头文件中,多个翻译单元#include,每个翻译单元将收到这些变量的私有副本

标记为const的全局变量默认具有内部链接,因此您不需要为它们显式指定static。因此,链接器不会抱怨全局const变量或标记为static的全局非const变量的多个定义,而在其他情况下它会抱怨(因为这些变量将具有外部链接)。

关于static数据成员,这是c++ 11标准第9.4.2/5段所说的:

static数据成员在命名空间作用域中具有外部链接(3.5)。一个地方性的班级不得有static数据成员。

这意味着如果你把它们的定义通过多个翻译单元放在头文件 #included中,你将在相应的目标文件中得到相同符号的多个定义(就像非const全局变量一样),不管它们的const限定是什么。在这种情况下,您的程序将违反单定义规则

此外,这个关于StackOverflow的问答可以让您更清楚地了解主题。