当你有一个包含静态成员的(非模板化)类时,如:
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.cpp
和translation_unit2.cpp
都包含foo.hpp
,那么{{1}的内存地址对于模板类Foo的一些实例化,(例如Foo<T>::x
)对于每个翻译单元是不同的?
答案 0 :(得分:3)
这很好,在这种情况下,在标题中定义它,编译器将确保只有一个实例,如果我们看到草案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;
答案 1 :(得分:1)
[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
是一个具有隐式声明的构造函数的类,就好像构造函数是在每个使用odr的翻译单元中隐式定义的,并且每个翻译单元中的隐式定义都应该调用基类的相同构造函数或D
的类成员。如果
D
是一个模板并且是在多个翻译单元中定义的,那么前面的要求既适用于模板定义中使用的模板封闭范围的名称,也适用于该点的相关名称实例化如果D
的定义满足所有这些要求,那么程序的行为就像D
的单一定义一样。如果D
的定义是不满足 这些要求,然后行为是未定义的。
答案 2 :(得分:1)
C ++ 11标准中的14.4完全回答了你的问题:
模板名称具有链接(3.5)。非成员函数模板可以具有内部链接;任何其他模板名称都应具有外部链接...
因此,类模板将始终具有外部链接,因此其静态数据成员(const或不是const)也是如此。因此,Foo<int>::x
将始终引用内存中的同一实体,无论此表达式出现在哪个转换单元中。链接器都会实现此目的。