为何在MSVC应用的参数化分配器中无法使用T类型非静态数据成员
分配器非静态成员引发MSVC链接错误的问题
我写了一个自定义分配器代码,如下:
#include <iostream> #include <map> #include <array> #include <vector> template<class T> struct ContiguousAllocator { public: using value_type = T; template<typename X> struct rebind { typedef ContiguousAllocator<X> other; }; ContiguousAllocator() noexcept {} template<class U> ContiguousAllocator(const ContiguousAllocator<U>&) noexcept { } T* allocate(std::size_t n) { return nullptr; } void deallocate(T* p, std::size_t n) {} private: std::vector<T> v; // 正常编译 static T t1; // 正常编译 static std::array<T, 10> t2; // 正常编译 static T t3[10]; // 正常编译 // T t4; // 取消注释触发链接错误 // std::array<T, 10> t5; // 取消注释触发链接错误 // T t6[10]; // 取消注释触发链接错误 }; int main(int argc, char* argv[]) { std::map<int, int, std::greater<int>, ContiguousAllocator<std::pair<const int, int>>> map; std::cout << map.size() << std::endl; return 0; }
只要取消注释任意一行标注// 取消注释触发链接错误的非静态成员,就会弹出如下链接错误:
1>ConsoleApplication1.obj : error LNK2019: unresolved external symbol "struct std::pair<int const ,int> __cdecl std::_Returns_exactly<struct std::pair<int const ,int> >(void)" (??$_Returns_exactly@U?$pair@$$CBHH@std@@@std@@YA?AU?$pair@$$CBHH@0@XZ) referenced in function "public: __cdecl std::_Tree_node<struct std::pair<int const ,int>,void *>::_Tree_node<struct std::pair<int const ,int>,void *>(void)" (??0?$_Tree_node@U?$pair@$$CBHH@std@@PEAX@std@@QEAA@XZ)
测试环境差异:
- GCC、Clang下代码能正常编译运行
- Visual Studio 2022(C++20 x64 Debug配置)和Godbolt上的MSVC都会出现上述链接错误
注:我知道这个分配器本身没法正常做内存分配,但问题出在链接阶段而非运行阶段,这是需要搞清楚的核心点。
问题原因分析
这是MSVC标准库的实现细节导致的:当分配器包含非静态的T类型成员时,std::map内部的std::_Tree_node节点类型的默认构造函数会触发std::_Returns_exactly<T>这个内部辅助函数的实例化,但在Debug模式下,MSVC没有为这个函数提供可链接的定义,从而抛出LNK2019未解析符号错误。
而GCC和Clang的标准库实现没有这种依赖逻辑,所以不会触发该错误。
静态成员为何不受影响?
静态成员属于类本身,不属于类的实例,编译器在实例化分配器对象时不会触发静态成员的构造逻辑(除非显式引用),因此不会触发MSVC标准库中那些导致链接错误的内部模板实例化流程。而非静态成员属于实例,当std::map的节点类型使用分配器时,会连带触发相关内部逻辑,进而引用到未定义的函数。
临时解决办法
如果要在MSVC下避开这个错误,暂时不要在分配器中添加非静态的T类型成员。如果必须存储相关数据,可以改用静态成员,或者把数据放在分配器外部,通过指针或引用访问。
内容的提问来源于stack exchange,提问作者qloq
相关产品推荐
相关产品推荐

