类模板中的静态成员变量

static member variable in class template

本文关键字:静态成员 变量      更新时间:2023-10-16

当你有一个包含静态成员的(非模板化)类时,例如:

class Foo
{
   public:
   static int x;
};

然后Foo::x必须在一个且只能一个翻译单元中定义,否则编译器将抱怨多个定义。 所以在somefile.cpp你必须定义它:

int Foo::x = 10;

这样,任何访问Foo::x的转换单元都在访问相同的内存地址。

但是,如果Foo是类模板呢?

template <class T>
class Foo
{
   public:
   static int x;
};

现在,可以在头文件中定义Foo<T>::x,方法是:

template <class T>
int Foo<T>::x = 10;
那么

,如果类模板Foo是在foo.hpp中定义的,并且translation_unit1.cpptranslation_unit2.cpp都包含foo.hpp,那么对于模板类Foo的某些实例化(例如Foo<int>::x)的Foo<T>::x的内存地址对于每个翻译单元会不同吗?

这很好,在这种情况下,在标头中定义它,编译器将确保只有一个实例,如果我们看到标准部分C++草案3.2 一个定义规则6段说(强调我的):

类类型(子句 9)、枚举类型 (7.2)、具有外部链接的内联函数 (7.1.2)、类模板(子句 14)、非静态函数模板 (14.5.6)、类模板的静态数据成员 (14.5.1.3)、成员函数可以有多个定义

然后我们可以转到第 14.5.1.3 部分 类模板的静态数据成员第 1 段说:

可以在包含静态成员类模板定义的命名空间作用域中提供静态数据成员的定义。

并提供以下示例:

template<class T> class X {
  static T s;
};
template<class T> T X<T>::s = 0;

您的问题在 C++11 标准中由 14.4 完美回答:

模板名称具有链接 (3.5)。非成员函数模板可以具有内部链接;任何其他模板名称都应具有外部链接...

因此,类模板将始终具有外部链接,其静态数据成员(常量或非常量)也是如此。因此,无论此表达式出现在哪个翻译单元中,Foo<int>::x都将始终引用内存中的同一实体。链接器使这发生。

[basic.def.odr]/6 在某些情况下明确允许对"类模板的静态数据成员"(以及其他例外)进行多个定义。

然后,对于这样的实体,它继续D

如果D的定义满足所有这些要求,那么程序的行为就好像有一个单一的D定义一样。如果D的定义不满足 这些要求,则行为是未定义的。

因此,在一个 TU 中实例化的模板的静态数据成员的地址与

在另一个 TU 中实例化的同一静态数据成员的地址相等。


完整的引用,我上面提到的段落我强调:

类类型、

枚举类型、具有外部链接的内联函数、类模板、非静态函数模板、静态数据成员可以有多个定义 类模板、类模板的成员函数或模板专用化 程序中未指定哪些模板参数,前提是每个定义 出现在不同的翻译单元中,前提是定义满足以下要求。给定这样一个名为D在多个翻译单元中定义的实体,则

  • D的每个定义应由相同的令牌序列组成;和
  • D的每个定义中,根据 3.4 查找的相应名称应指D定义中定义的实体,或应指代同一实体,在重载解决和部分模板专用化匹配后,除了名称可以引用具有内部链接或没有链接的const对象,如果该对象在所有定义中具有相同的文字类型D, 并且对象用常量表达式初始化,并使用对象的值(但不是地址),并且对象在D的所有定义中具有相同的值;和
  • D的每个定义中,相应的实体应具有相同的语言联系;以及
  • 在每个D定义中,所指的重载运算符,对转换函数、构造函数、运算符新函数和运算符删除函数的隐式调用,应指同一函数,或在D定义中定义的函数;
  • D的每个定义中,(隐式或显式)函数调用使用的默认参数被视为其标记序列存在于D的定义中;也就是说,默认参数受上述三个要求的约束(并且,如果默认参数具有带有默认参数的子表达式,则此要求递归适用)。
  • 如果 D 是具有隐式声明构造函数的类,则就好像构造函数是在每个使用它的翻译单元中隐式定义的,并且每个翻译单元中的隐式定义应为 D 的基类或类成员调用相同的构造函数。

如果D是模板,并且在多个翻译单元中定义,则上述要求应既适用于模板定义中使用的模板封闭范围的名称,也适用于实例化点的从属名称。如果D的定义满足所有这些要求,那么程序的行为就好像有一个单一的D定义一样。如果D的定义不满足 这些要求,则行为是未定义的。