如果模板类定义包含依赖于模板类型的静态成员变量,我不确定可靠行为应该是什么?
在我的情况下,最好将静态成员的定义放在与类定义相同的.h文件中,因为
MyClass<int>
,一个适用于所有MyClass<double>
等等。我可以最简单地说,列出的代码at this link在使用gcc 4.3编译时的行为完全符合我的要求。这种行为是否符合C ++标准,以便在使用其他编译器时可以依赖它?
该链接不是我的代码,而是由CodeMedic发布到讨论here的反例。我发现了其他几个像one这样的辩论,但我认为没有任何结论。
我认为链接器正在整合找到的多个定义(在示例a.o
和b.o
中)。
这是必需/可靠的链接器行为吗?
答案 0 :(得分:20)
来自N3290,14.6:
类模板的静态数据成员应在中定义 每个翻译单元都隐式实例化[...],除非相应的专业化被明确地实例化[...]。
通常,您将静态成员定义与模板类定义一起放在头文件中:
template <typename T>
class Foo
{
static int n; // declaration
};
template <typename T> int Foo<T>::n; // definition
扩展特许权:如果您计划在代码中使用显式实例化,例如:
template <> int Foo<int>::n = 12;
然后你必须不将模板化的定义放在标题中如果Foo<int>
也用于除包含显式实例化的TU之外的其他TU,那么你就会获得多个定义
但是,如果确实需要为所有可能的参数设置初始值而不使用显式实例化,则必须将其放在标题中,例如:与TMP:
// in the header
template <typename T> int Foo<T>::n = GetInitialValue<T>::value; // definition + initialization
答案 1 :(得分:2)
这完全是对@Kerrek SB的优秀答案的补充。我将它添加为注释,但已经有很多注释,所以默认情况下会隐藏新注释。
所以,我看到的他和其他例子在某种意义上是“容易的”,因为事先知道静态成员变量的类型。这很容易,因为编译器例如知道任何模板实例化的存储大小,因此可以认为编译器可以使用时髦的修改方案,输出变量定义一次,并将其余部分卸载到链接器,这甚至可以工作。
但是当静态成员类型依赖于模板参数时,它有点令人惊讶。例如,以下作品:
template <typename width = uint32_t>
class Ticks : public ITimer< width, Ticks<width> >
{
protected:
volatile static width ticks;
}
template <typename width> volatile width Ticks<width>::ticks;
(请注意,静态var的显式实例化不需要(或允许)“width”的默认规范。)
因此,它带来了更多的想法,C ++编译器必须做很多处理 - 特别是,实例化模板,不仅需要模板本身,而且还必须收集所有[静态成员]显式实例化(人们可能只是想知道为什么他们被制作成单独的句法结构,而不是在模板类中拼写出来的东西)。
至于链接器级别的实现,对于GNU binutils,它的“常用符号”: http://sourceware.org/binutils/docs/as/Comm.html#Comm。 (对于Microsoft工具链,它的名字叫做COMDAT,正如另一个答案所说)。
答案 2 :(得分:1)
链接器处理此类情况几乎与应用了__declspec(selectany)声明的非模板类静态成员完全相同,如下所示:
class X {
public:
X(int i){};
};
__declspec(selectany) X x(1);//works in msvc, for gcc use __attribute__((weak))
并且为msdn says:“在链接时,如果看到COMDAT的多个定义,链接器会选择一个并丢弃其余的...对于动态初始化的全局对象,selectany将丢弃未引用的对象的初始化代码,以及。“