为何两段同类问题的C++代码一段报错一段正常?如何修改复现错误?
C++静态成员多重定义问题:模板与非模板的差异
1. 非模板类的报错场景
简单程序包含foo.h、foo.cc、main.cc三个文件,其中foo.h的代码如下:
class foo{ static const int array[3]; }; const int foo::array[3] = { 1, 2, 3 }; // 引发错误的行
编译链接命令:
clang++ -c *.cc -std=c++17 clang++ *.o -o a.out -std=c++17
报错信息:
main.o:(.rodata+0x0): multiple definition of `foo::array' foo.o:(.rodata+0x0): first defined here clang-14: error: linker command failed with exit code 1 (use -v to see invocation)
2. 模板类的无报错场景
复杂程序包含t_a.h、t_b.h、t_i.h、t_r.h、use_t_i_1.cc、use_t_i_2.cc,其中t_i.h中对模板类TIT的静态成员kT做了实体定义:
template <typename BaseT, typename DerivedT> const TI<BaseT> TIT<BaseT, DerivedT>::kT = RST<BaseT>(DerivedT::name()); // 本应引发类似错误但未出现
使用完全相同的编译链接命令,却没有报错。
差异原因
C++标准对非模板实体和模板实体的链接规则做了明确区分:
- 非模板类静态成员:如果在头文件中直接定义实体(比如
foo::array的初始化),每个包含该头文件的源文件都会生成一份实体副本。链接器合并目标文件时,会检测到相同符号的多份定义,触发多重定义错误。 - 模板类静态成员:模板本身不生成实体,只有被实例化时才会生成具体代码。C++标准规定,头文件中的模板静态成员定义属于特殊的“inline”性质处理——只要各翻译单元实例化的模板参数组合一致,链接器会自动合并重复的实例化副本,不会报错。
修改复杂程序触发多重定义错误的方法
要让模板类静态成员也触发相同错误,需要打破模板的自动合并规则,具体步骤如下:
- 将模板静态成员的定义从
t_i.h头文件移出,放到单独的源文件(比如t_i.cc)中。 - 在
t_i.h中仅保留模板类的声明和静态成员的声明,不写实体定义。 - 在
t_i.cc中添加静态成员的定义,并显式实例化某一组模板参数;同时在use_t_i_1.cc、use_t_i_2.cc中也显式实例化完全相同的参数组合。
示例代码修改:
t_i.h(仅保留声明):
template <typename BaseT, typename DerivedT> class TIT { public: static const TI<BaseT> kT; };
t_i.cc(添加定义+显式实例化):
#include "t_i.h" #include "t_r.h" template <typename BaseT, typename DerivedT> const TI<BaseT> TIT<BaseT, DerivedT>::kT = RST<BaseT>(DerivedT::name()); // 显式实例化某一组参数 template class TIT<SomeBase, SomeDerived>;
use_t_i_1.cc和use_t_i_2.cc(添加相同的显式实例化):
#include "t_i.h" template class TIT<SomeBase, SomeDerived>;
此时用原命令编译链接,链接器会发现多个目标文件中存在TIT<SomeBase, SomeDerived>::kT的重复定义,从而触发和非模板类一致的多重定义错误。
内容的提问来源于stack exchange,提问作者piratesailor
相关产品推荐
相关产品推荐

