含非类型模板参数的模板类调用方法时出现链接错误求助
模板类链接错误原因分析
问题场景
定义带非类型模板参数的模板类ArrayEnc,将接口放在头文件ArrEnc.h,所有方法实现放在单独编译单元ArrayEnc.cpp。在User类中构造ArrayEnc<6>对象并使用时出现链接错误,但把实现移到头文件后错误消失。
头文件ArrEnc.h内容
#pragma once #include <array> #include <iostream> #include <string> template <size_t size> class ArrayEnc { public: ArrayEnc() noexcept; ArrayEnc(const std::array<int, size>& arr, const std::string& usage = "DefaultUsage"); ArrayEnc(const int arr[size], const std::string& usage); template <size_t sz> friend std::ostream& operator<<(std::ostream& os, const ArrayEnc<sz>& ae); private: std::array<int, size> m_Arr; std::string usageHint; }; template<size_t size> ArrayEnc<size> ::ArrayEnc() noexcept :m_Arr() {} template <size_t size> ArrayEnc<size> ::ArrayEnc(const std::array<int, size>& arr, const std::string& usage) :m_Arr(std::move(arr)), usageHint(usage) {} template <size_t size> ArrayEnc<size> ::ArrayEnc(const int arr[size], const std::string& usage) :m_Arr(arr), usageHint(usage) {} template<size_t size> std::ostream& operator<<(std::ostream& os, const ArrayEnc<size>& ae) { os << "Usage Hint: "<<ae.usageHint<<std::endl; for(const auto& elem : ae.m_Arr) { os << elem<<", "; } return os; }
实现文件ArrayEnc.cpp内容
#include "ArrayEnc.h" template<size_t size> ArrayEnc<size> ::ArrayEnc() noexcept :m_Arr() {} template <size_t size> ArrayEnc<size> ::ArrayEnc(const std::array<int, size>& arr, const std::string& usage) :m_Arr(std::move(arr)), usageHint(usage) {} template <size_t size> ArrayEnc<size> ::ArrayEnc(const int arr[size], const std::string& usage) :m_Arr(arr), usageHint(usage) {} template<size_t size> std::ostream& operator<<(std::ostream& os, const ArrayEnc<size>& ae) { os << "Usage Hint: "<<ae.usageHint<<std::endl; for(const auto& elem : ae.m_Arr) { os << elem<<", "; } return os; }
测试文件Test.cpp内容
#include "ArrayEnc.h" class User { public: User() :array( { 0, 1, 2, 2, 3, 0 } ) {} void display() const noexcept { std::cout<<array<<std::endl; } private: ArrayEnc<6> array; }; int main() { User u; u.display(); }
编译命令
g++ -lstdc++ -std=c++17 ArrayEnc.cpp Test.cpp -o Test
错误信息
C:\Users\UserName\AppData\Local\Temp\cchYeN1s.o: In function `User::User()': C:\Users\UserName\Dev\C++/./Test.cpp:12: undefined reference to `ArrayEnc<6ull>::ArrayEnc(std::array<int, 6ull> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)' C:\Users\UserName\AppData\Local\Temp\cchYeN1s.o: In function `User::display() const': C:\Users\UserName\Dev\C++/./Test.cpp:17: undefined reference to `std::ostream& operator<< <6ull>(std::ostream&, ArrayEnc<6ull> const&)' collect2.exe: error: ld returned 1 exit status
原因分析
C++模板是代码生成器,编译器只有在看到模板的具体实例化(比如ArrayEnc<6>)时,才会生成对应的类和方法的机器码。
当模板实现放在单独的.cpp文件时,编译ArrayEnc.cpp单元时,编译器不知道后续会实例化ArrayEnc<6>,所以不会生成该实例对应的构造函数和operator<<代码。编译Test.cpp时,编译器看到ArrayEnc<6>的使用,但只有头文件中的模板声明,无法生成具体实现,只能生成引用符号,等链接阶段找对应机器码。但链接阶段找不到ArrayEnc<6>相关方法的实现,因此报未定义引用错误。
把实现移到头文件后,编译Test.cpp时编译器能直接拿到模板的完整实现,当场生成ArrayEnc<6>的所有方法代码,链接阶段能找到对应符号,错误自然消失。
解决方法
有两种常见的解决方式:
- 模板实现全部放在头文件:这是最通用的做法,确保编译器在实例化模板时能获取完整的实现代码。
- 显式实例化模板:在
ArrayEnc.cpp末尾添加显式实例化代码,告诉编译器提前生成指定模板参数的实例:
注意:这种方式的局限性是只能提前指定需要用到的模板参数,后续如果用到其他参数(比如template class ArrayEnc<6>; template std::ostream& operator<< <6>(std::ostream&, const ArrayEnc<6>&);ArrayEnc<8>),需要添加对应的显式实例化代码。
内容的提问来源于stack exchange,提问作者Aayush Anand
相关产品推荐
相关产品推荐

