问询:GCC与MSVC对隐式定义构造函数的符号导出差异
问题:GCC与MSVC在导出含不可拷贝成员类时的行为差异
代码示例
struct Foo { Foo(int &a) : m_a(a){} int &m_a; }; struct CORE_EXPORT Bar { std::vector<Foo> f; };
现象与疑问
- 使用GCC并添加
-fvisibility=hidden参数,同时将Bar标记为默认可见性时,代码编译正常。 - 使用MSVC并为Bar添加
__declspec(dllexport)标记时,因Foo::operator=被删除而出现编译错误。 - 不为Bar添加
__declspec(dllexport)标记时,MSVC下代码可正常编译。
已知MSVC底层会生成并导出Bar的拷贝构造函数,进而尝试使用Foo的拷贝构造函数,现疑问:
- 为何GCC与MSVC会出现这种行为差异?
- MSVC导出了哪些GCC未导出的隐式定义符号?
解答
行为差异的核心原因
GCC和MSVC在处理类导出时的隐式特殊成员函数实例化时机与导出规则存在本质区别:
- GCC在
-fvisibility=hidden模式下,仅会导出**被ODR使用(单定义规则)**或显式声明的特殊成员函数。对于Bar的拷贝构造、拷贝赋值等函数,只要代码中没有实际调用它们,GCC不会主动实例化这些函数,自然也不会检查Foo的拷贝操作可行性。 - MSVC在标记
__declspec(dllexport)时,会强制实例化类的所有隐式声明的特殊成员函数(包括拷贝构造、拷贝赋值、移动构造、移动赋值等)并尝试导出——无论这些函数是否被代码调用。由于Foo包含引用成员,其拷贝赋值运算符被隐式删除,Bar的拷贝赋值运算符在实例化时会依赖std::vector<Foo>的赋值操作,而vector的赋值需要调用Foo的赋值运算符,最终触发编译错误。
MSVC额外导出的隐式符号
当Bar被标记__declspec(dllexport)时,MSVC会尝试导出以下GCC不会主动生成/导出的隐式特殊成员函数:
Bar::Bar(const Bar&):Bar的拷贝构造函数,其实现依赖std::vector<Foo>的拷贝构造逻辑;Bar& Bar::operator=(const Bar&):Bar的拷贝赋值运算符,这是触发报错的核心——它需要调用std::vector<Foo>::operator=,而vector的赋值操作会尝试调用Foo的赋值运算符,而Foo的赋值运算符因引用成员被隐式删除,导致编译失败。
简言之,MSVC的导出机制会强制触发类所有隐式特殊成员函数的实例化,而GCC仅在这些函数被实际使用时才会实例化,这就是两者行为差异的根源。
内容的提问来源于stack exchange,提问作者jjcasmar
相关产品推荐
相关产品推荐

