我无法理解,为什么如果我们在头文件中定义普通(非模板)类的静态变量,我们有链接器错误,但是在模板的情况下一切正常,而且我们将有单个静态变量实例所有翻译单位:
它是模板标题(template.h):
// template.h
template<typename T>
class Templ {
public:
static int templStatic;
};
template<typename T> Templ<T>::templStatic = 0;
这是第一个使用模板的单位(unit1.cpp)
// unit1.cpp
#include "template.h"
int method1() {
return Templ<void>::templStatic++;
}
这里的第二个单位(unit2.cpp):
// unit2.cpp
#include "template.h"
int method2() {
return Templ<void>::templStatic++;
}
最后,main.cpp:
// main.cpp
#include <iostream>
int method1();
int method2();
int main(int argc, char** argv) {
std::cout << method1() << std::endl;
std::cout << method2() << std::endl;
}
在编译,链接和执行此代码后,我们将得到以下输出:
0
1
那么,为什么在模板的情况下一切正常(和预期一样)?编译器或链接器如何处理这个问题(我们可以在分离的编译器调用中编译每个.cpp文件,然后将它们与caling链接到链接器,这样编译器和链接器不会同时“看到”所有.cpp文件)?
PS:我的编译器:msvcpp 9(但也检查了mingw)
答案 0 :(得分:61)
这是因为静态数据成员的定义本身就是一个模板。允许这样做是必要的,原因与允许您在程序中多次不内联的功能模板一样。您需要模板来生成结果实体(例如,函数或静态数据成员)。如果您不允许放置静态数据成员的定义,您将如何实例化以下
template<typename T>
struct F {
static int const value;
};
template<typename T>
int const F<T>::value = sizeof(T);
不知道T
是什么 - 标准表示类模板外的定义是模板定义,其中参数是从其类模板所有者继承的。
我在GCC做了一些实验。在下文中,我们有一个F<float>::value
的隐式实例化,以及F<char>::value
的一个显式特化,它必须在.cpp文件中定义,以便在多次包含时不会导致重复的符号错误。
// Translation Unit 1
template<typename T>
struct F {
static int value;
};
template<typename T>
int F<T>::value = sizeof(T);
// this would belong into a .cpp file
template<> int F<char>::value = 2;
// this implicitly instantiates F<float>::value
int test = F<float>::value;
int main() { }
第二个翻译单元只包含同一静态数据成员的另一个隐式实例
template<typename T>
struct F {
static int value;
};
template<typename T>
int F<T>::value = sizeof(T);
int test1 = F<float>::value;
以下是我们通过GCC获得的内容 - 它将每个隐式实例化变为弱符号,并将其粘贴到自己的部分中。当链接时存在多个符号时,弱符号不会导致错误。相反,链接器将选择一个实例,并丢弃其他实例,假设它们都是相同的
objdump -Ct main1.o # =>
# cut down to the important ones
00000000 l df *ABS* 00000000 main1.cpp
0000000a l F .text 0000001e __static_initialization_and_destruction_0(int, int)
00000000 l d .data._ZN1FIfE5valueE 00000000 .data._ZN1FIfE5valueE
00000028 l F .text 0000001c global constructors keyed to _ZN1FIcE5valueE
00000000 g O .data 00000004 F<char>::value
00000000 g O .bss 00000004 test
00000000 g F .text 0000000a main
00000000 w O .data._ZN1FIfE5valueE 00000004 F<float>::value
因为我们可以看到F<float>::value
是一个弱符号,这意味着链接器可以在链接时看到其中的多个。 test
,main
和F<char>::value
是全局(非弱)符号。将main1.o
和main2.o
链接在一起,我们在地图输出(-Wl,-M
)中看到以下
# (mangled name)
.data._ZN1FIfE5valueE
0x080497ac 0x4 main1.o
0x080497ac F<float>::value
这表示实际上除了一个实例之外它都会丢弃。
答案 1 :(得分:2)
有解决方案,你可以创建一个父类并将静态变量放入其中,然后让你的模板类私下继承它,这是一个例子:
class Parent
{
protected:
static long count;
};
long Parent::count = 0;
template<typename T>
class TemplateClass: private Parent
{
private:
int mKey;
public:
TemplateClass():mKey(count++){}
long getKey(){return mKey;}
}
int main()
{
TemplateClass<int> obj1;
TemplateClass<double> obj2;
std::cout<<"Object 1 key is: "<<obj1.getKey()<<std::endl;
std::cout<<"Object 2 key is: "<<obj2.getKey()<<std::endl;
return 0;
}
输出将是:
Object 1 key is: 0
Object 2 key is: 1
答案 2 :(得分:0)
这是因为模板代码不是源代码。这是有关如何编写源代码的说明。
非模板静态变量是实际的源代码,并且编译器将尝试通过两次包含某些内容来完全按照您说的做。因此,您必须在.cpp文件中初始化静态变量,并且只能在描述该类的.h文件中引用它。它等效于通过extern声明的全局变量。
当编译器看到
template<class T> Templ{...};
除了记下模板存在以外,它什么都不做。就其而言,没有与Templ相关的源代码。 第一次实际使用
Templ<int> Instance
编译器查看与Templ相关的所有template <>代码,并使用它来构造.h和.cpp文件(仅在编译期间存在)。这些文件可能看起来像这样:
Temple_int.h
class Templ_int{
public:
static int templStatic;
};
Templ_int.cpp
#include "Templ_int.h"
Templ_int::templStatic = 0;
每个
Templ<int>
成为一个Templ_int。 因此,在编译器创建的.cpp文件中,用于初始化静态变量的源代码仅存在一次。 (显然,此过程的实际特定于编译器的实现对于创建与模板名称相似的类等将是健壮的。)