将带自定义哈希的unordered_set作为结构体成员引发-Wsubobject-linkage警告
我尝试把std::unordered_set<std::pair<int, int>>作为结构体成员,需要自定义哈希函数,但不想重载std::hash<std::pair<int, int>>,于是写了如下代码:
test.hpp
#ifndef TEST_HPP #define TEST_HPP #include <unordered_set> struct myStruct { constexpr static auto pair_hash = [](const std::pair<int, int>& p) -> size_t { return std::hash<int>{}(p.first) ^ (std::hash<int>{}(p.second) << 1); }; std::unordered_set<std::pair<int, int>, decltype(pair_hash)> mySet; }; #endif
test.cpp
#include "test.hpp" int main() { myStruct s; return 0; }
代码能编译运行,但触发警告:
‘myStruct’ has a field ‘std::unordered_set<std::pair<int, int>, const myStruct::<lambda(const std::pair<int, int>&)> > myStruct::mySet’ whose type has internal linkage [-Wsubobject-linkage]
原因分析
这个警告的核心是lambda的类型具有内部链接属性:你在myStruct内部定义的static constexpr lambda,它的类型是编译器生成的匿名类,作用域被限定在myStruct内部,属于内部链接(只能在当前编译单元访问)。
但std::unordered_set的第二个模板参数用了这个lambda的类型decltype(pair_hash),导致mySet的类型也带有内部链接属性。如果myStruct被多个编译单元包含(比如其他cpp文件也include test.hpp),不同编译单元里的mySet类型会被视为不同的类型,这会引发链接错误或者未定义行为,因此编译器提前发出警告。
修复方案
方案一:用独立的函数对象类替代lambda
把哈希逻辑封装成一个全局(或命名空间内)的函数对象类,它的类型具有外部链接,能被所有编译单元共享:
test.hpp
#ifndef TEST_HPP #define TEST_HPP #include <unordered_set> // 独立的哈希函数对象类 struct PairHash { size_t operator()(const std::pair<int, int>& p) const { return std::hash<int>{}(p.first) ^ (std::hash<int>{}(p.second) << 1); } }; struct myStruct { // 使用外部链接的PairHash作为哈希类型 std::unordered_set<std::pair<int, int>, PairHash> mySet; }; #endif
方案二:将lambda移到结构体外部(命名空间级别)
把lambda定义在命名空间中,让它的类型具有外部链接,同时在结构体中初始化mySet时传入lambda实例:
test.hpp
#ifndef TEST_HPP #define TEST_HPP #include <unordered_set> namespace my_utils { // 命名空间级别的constexpr lambda,类型具有外部链接 constexpr auto pair_hash = [](const std::pair<int, int>& p) -> size_t { return std::hash<int>{}(p.first) ^ (std::hash<int>{}(p.second) << 1); }; } // namespace my_utils struct myStruct { // 使用外部链接的lambda类型,并传入实例初始化mySet std::unordered_set<std::pair<int, int>, decltype(my_utils::pair_hash)> mySet{my_utils::pair_hash}; }; #endif
方案三:使用std::reference_wrapper(C++20及以后)
如果坚持要在结构体内部保留lambda,可以用std::reference_wrapper把lambda包装成外部链接的类型,但需要确保lambda的生命周期覆盖mySet的使用周期:
test.hpp
#ifndef TEST_HPP #define TEST_HPP #include <unordered_set> #include <functional> struct myStruct { constexpr static auto pair_hash = [](const std::pair<int, int>& p) -> size_t { return std::hash<int>{}(p.first) ^ (std::hash<int>{}(p.second) << 1); }; // 用reference_wrapper包装lambda类型,消除内部链接问题 std::unordered_set<std::pair<int, int>, std::reference_wrapper<decltype(pair_hash)>> mySet{std::ref(pair_hash)}; }; #endif
内容的提问来源于stack exchange,提问作者Horst Kretschmer

