继承unordered_map的类为何需operator==?EXPORT修饰符影响编译的原因?
问题背景
现有如下C++代码:
#include <unordered_map> #define LIBRARY #if defined(LIBRARY) # define EXPORT __declspec(dllexport) #else # define EXPORT __declspec(dllimport) #endif struct Base { double d1; double d2; double d3; /* 取消注释以下代码即可编译通过 */ //bool operator==(const Base& rhs) const { // return true; //} }; struct EXPORT Derived : public std::unordered_map<int, Base> {}; int main() { Derived d; return 0; }
编译时触发如下错误:
1>D:\VS2022\VC\Tools\MSVC\14.39.33519\include\xhash(246,8): 1>see declaration of 'std::_Hash_vec<std::allocator<std::_List_unchecked_iterator<std::_List_val<std::_List_simple_types<_Ty>>>>>' 1> with 1> [ 1> _Ty=std::pair<const int,Base> 1> ] 1>D:\VS2022\VC\Tools\MSVC\14.39.33519\include\utility(491,56): error C2676: binary '==': 'const _Ty2' does not define this operator or a conversion to a type acceptable to the predefined operator 1>D:\VS2022\VC\Tools\MSVC\14.39.33519\include\utility(491,56): error C2676: with 1>D:\VS2022\VC\Tools\MSVC\14.39.33519\include\utility(491,56): error C2676: [ 1>D:\VS2022\VC\Tools\MSVC\14.39.33519\include\utility(491,56): error C2676: _Ty2=Base 1>D:\VS2022\VC\Tools\MSVC\14.39.33519\include\utility(491,56): error C2676: ]
已知两种可行的编译修复方案:
- 为
Base类显式定义const版本的operator==运算符; - 移除
Derived类的EXPORT修饰符。
问题解答
1. 为何继承unordered_map的类需要定义operator==?
std::unordered_map本身并不强制要求值类型实现operator==——只有当代码实际调用到容器的operator==时才会触发这个依赖。但这里的核心原因是MSVC的导出机制触发了未使用成员函数的实例化:
当用__declspec(dllexport)导出Derived类时,编译器需要为该类生成完整的导出符号表,这包括解析从std::unordered_map继承来的所有可访问成员函数。而std::unordered_map的operator==在实例化时,会递归要求其值类型(即Base)支持==比较——因为两个哈希表相等的判定逻辑是:所有键对应的value都要相等。
如果没有导出Derived类,编译器采用“按需实例化”策略:你的main函数只创建了对象,没调用operator==,所以编译器不会去实例化这个函数,自然也不会检查Base是否有operator==。但加上EXPORT后,编译器为了生成导出符号,必须实例化这个函数,进而触发错误。
2. EXPORT修饰符如何影响编译过程?
__declspec(dllexport)的核心作用是标记类需要被导出到DLL的符号表,供外部模块使用。为了完成这个导出,编译器会执行以下特殊处理:
- 强制解析类的所有可访问成员函数(包括继承自基类的),确保它们能被正确实例化并生成导出符号;
- 对于模板基类(比如
std::unordered_map),编译器会实例化基类的所有可能的成员函数实例,而不仅仅是代码中用到的。
这就导致原本不会被触发的模板依赖检查被激活——比如unordered_map::operator==对Base::operator==的依赖。反之,去掉EXPORT后,编译器回到常规的“按需实例化”,只处理代码中实际调用的部分,因此不会触发这个检查。
内容的提问来源于stack exchange,提问作者tomato potato

