C++在使用模版(template)类的时候,如果像通常那样将类成员函数的声明和实现分别放在.h
和.cpp
中,会导致在编译时会报错undefined reference to
,找不到对应成员函数。
起因
在实现一个模板类的时候遇到了一个问题:
1 | // Matrix.h |
编译时会报错:
1 | CMakeFiles\AlgorithmWarehouse.dir/objects.a(main.cpp.obj): In function `main': |
很迷不知道为什么会找不到构造函数,查了一圈确定是因为使用template<typename T>
的原因,总结一下。
原理
下面使用一个简单的例子来叙述这个问题:假设要实现一个栈,有两个文件Stack.h
和Stack.cpp
,栈声明使用template<typename T>
,main.cpp
在import "Stack.h"
后调用这个类。
1 | // Stack.h |
编译下来会报错:undefined reference to
因为template
其实是一种类似语法糖的东西,C++中每一个对象所占用的空间大小,是在编译的时候就确定的,在模板类没有真正的被使用之前,编译器是无法知道,模板类中使用模板类型的对象的所占用的空间的大小的。只有模板被真正使用的时候,编译器才知道,模板套用的是什么类型,应该分配多少空间。这也就是模板类为什么只是称之为模板,而不是泛型的缘故。
既然是在编译的时候,根据套用的不同类型进行编译,那么,套用不同类型的模板类实际上就是两个不同的类型,也就是说,stack<int>
和stack<char>
是两个不同的数据类型,他们共同的成员函数也不是同一个函数,只不过具有相似的功能罢了。
所以模版本质其实和宏差不多,不同于原本的类型定义。编译器在编译main.cpp
时会根据需要隐式实例化诸如 Stack<int>
,如果这个实例并没有在main.cpp
中,编译器就会根据include去找,但是显然编译时Stack.h
中并没有对Stack<int>
的声明,于是就报错了。
解决方法
显式声明
在Stack.cpp
中显式声明会用到的类型:
1 | template class Stack<int>; |
我认为的最好的办法,但缺点也很显然,如果需要一种新的数据类型就很难受,必须手动再添加,封装上不太友好。
全放到头文件中
很直接,把所有Stack.cpp
中的源代码全放到Stack.h
中,不需要进行任何修改。但缺点就是如果这个头文件在很多文件中被使用的话,会影响编译速度,但不会产生任何链接问题,因为编译器会忽略重复实现。
把实现代码移至一个新头文件中
把Stack.cpp
改为Stack_impl.h
,然后在Stack.h
里面import "Stack_impl.h"
,这样能保证代码实现和声明在不同的文件中,但本质上和上一个解决办法是一样的。