为什么链接器找不到C++类的模板静态成员?分离编译链接报错
问题现象
按如下方式声明实现类的模板方法时,编译无报错但链接失败:
unit.h头文件中声明类与静态模板方法:
class unit { ... template<typename T> static void *marshal(void *buff, const std::string &name, T value); ... };
unit.cpp源文件中实现该模板方法:
template<typename T> void *unit::marshal(void *buff, const std::string &name, T value) { ... return buff; }
- 其他代码位置调用该方法,传入int类型实参:
... unsigned char buffer[100], *end = nullptr; end = (unsigned char *) unit::marshal(buffer, "Chimes1_Volume", 222); ...
链接阶段抛出错误:
undefined reference to `void* AW::unit::marshal<int>(void*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int)'
将该方法改为友元函数时依然出现完全相同的错误;如果把方法实现直接移动到头文件的类声明内部,程序可以正常编译运行,但需要明确.h/.cpp分离写法的问题所在。
根本原因
这个问题和方法是static还是friend没有关系,本质是C++模板的编译机制决定的:
- C++的模板不是可以直接链接的实体代码,本质是生成代码的规则模板。只有当编译器遇到带具体类型实参的模板调用时,才会按需生成对应类型的实际函数代码,这个过程叫模板实例化。
- 模板实例化要求编译器在调用点必须能看到模板的完整实现,否则无法生成对应类型的函数代码。你把模板实现写在独立的
unit.cpp文件中时:- 编译
unit.cpp阶段,编译器看不到外部会用什么类型调用这个模板,不会自动生成任何具体类型的marshal函数实体 - 编译调用marshal的其他源文件时,编译器只能看到头文件里的模板声明,看不到写在cpp里的实现,没法自行生成
marshal<int>的实体代码
- 编译
- 最终链接阶段,所有目标文件里都找不到
marshal<int>对应的符号,就会抛出未定义引用的错误。
你把实现挪到头文件类内部后,所有包含该头文件的调用点都能直接看到模板的完整实现,编译器可以在调用点直接生成对应类型的实例代码,自然就能正常链接运行。
解决方案
两种合规的实现方式可以选:
- 模板实现放头文件(通用方案)
这是绝大多数C++模板代码的标准写法,STL等模板库全部采用这种方式。你既可以把实现直接写在类声明内部,也可以在类声明结束后,把模板实现写在同一个头文件里,不需要放在cpp中。 - cpp内显式实例化固定类型(适用类型确定的场景)
如果你明确知道这个模板只会被有限的几个类型调用,可以在unit.cpp中模板实现的后面,手动声明需要用到的模板实例,告诉编译器提前生成对应类型的函数实体。比如你当前调用用到了int类型,就在unit.cpp末尾添加:
添加后编译unit.cpp时就会生成// 显式生成int版本的marshal函数实体 template void* unit::marshal<int>(void* buff, const std::string& name, int value);marshal<int>的对应符号,链接阶段就能正常找到。如果后续需要用其他类型(比如double、long等)调用该方法,必须补充对应类型的显式实例化声明,否则依然会报链接错误。
内容的提问来源于stack exchange,提问作者ZioByte
相关产品推荐
相关产品推荐

