如何重载/修改std::unordered_set的find方法,支持以对象子集为键查找?
实现std::unordered_set的异构查找
要让std::unordered_set支持直接用裸键(比如示例中的字符串)查找对象,核心问题在于标准库默认的哈希和比较逻辑只针对容器的value_type(即userData_t)。你之前的代码没生效,是因为C20之前的unordered_set不支持异构查找,而C20之后需要明确标记哈希和比较器为透明,才能让find接受不同类型的参数。
一、C++20 标准解决方案(推荐)
C++20 引入了透明哈希与透明比较器的特性,只需让你的哈希函数和相等比较器支持多种类型,并标记is_transparent,就能实现异构查找:
1. 定义透明哈希函数
struct UserDataHash { // 计算userData_t的哈希值 std::size_t operator()(const userData_t& obj) const { return std::hash<std::string>()(obj.name); } // 直接计算键(字符串)的哈希值 std::size_t operator()(const std::string& key) const { return std::hash<std::string>()(key); } // 标记为透明哈希,告诉标准库支持异构类型 using is_transparent = void; };
2. 定义透明相等比较器
struct UserDataEqual { // 两个userData_t对象比较 bool operator()(const userData_t& lhs, const userData_t& rhs) const { return lhs.name == rhs.name; } // userData_t与字符串键比较 bool operator()(const userData_t& obj, const std::string& key) const { return obj.name == key; } // 字符串键与userData_t对象比较(对称重载,避免顺序问题) bool operator()(const std::string& key, const userData_t& obj) const { return key == obj.name; } // 标记为透明比较器 using is_transparent = void; };
3. 声明并使用unordered_set
std::unordered_set<userData_t, UserDataHash, UserDataEqual> employees; // 添加对象 employees.insert({"Pete Johnson", ...}); // 假设userData_t有合适的构造函数 // 直接用字符串查找 auto it = employees.find("Pete Johnson"); if (it != employees.end()) { // 找到对应userData_t对象 }
这个方案完全符合你的需求:不需要额外存储键的副本,也不需要创建dummy对象,find直接接受裸键即可。
二、C++20 之前的兼容方案
如果无法使用C++20,有两种可选方案:
方案1:封装自定义集合类(线性查找,适合小数据量)
自己封装一个类,内部持有std::unordered_set,并提供支持裸键查找的find方法:
#include <algorithm> template<typename T, typename Key, typename GetKeyFunc> class HeteroUnorderedSet { private: std::unordered_set<T> m_set; GetKeyFunc m_getKey; public: explicit HeteroUnorderedSet(GetKeyFunc getKey) : m_getKey(std::move(getKey)) {} void insert(T obj) { m_set.insert(std::move(obj)); } auto find(const Key& key) { return std::find_if(m_set.begin(), m_set.end(), [&](const T& obj) { return m_getKey(obj) == key; }); } // 按需添加erase、size等其他方法 }; // 使用方式 HeteroUnorderedSet<userData_t, std::string, decltype(&getKey)> employees(getKey);
注意:这个方案的find是线性遍历,数据量大时效率不如哈希查找。
方案2:维护轻量映射表(哈希查找,适合大数据量)
如果需要保持哈希查找的效率,可以内部维护一个std::unordered_map<Key, const T*>,但要注意对象的生命周期管理:
template<typename T, typename Key, typename GetKeyFunc> class HashHeteroSet { private: std::unordered_set<T> m_set; std::unordered_map<Key, const T*> m_keyMap; GetKeyFunc m_getKey; public: explicit HashHeteroSet(GetKeyFunc getKey) : m_getKey(std::move(getKey)) {} void insert(T obj) { auto [it, inserted] = m_set.insert(std::move(obj)); if (inserted) { m_keyMap[m_getKey(*it)] = &*it; } } auto find(const Key& key) { auto mapIt = m_keyMap.find(key); if (mapIt != m_keyMap.end()) { return m_set.find(*mapIt->second); } return m_set.end(); } // 注意:erase方法需要同步更新m_keyMap,避免悬空指针 };
这个方案避免了键的副本(存储的是指针),但需要额外维护映射表,实现复杂度稍高。
内容的提问来源于stack exchange,提问作者Qwert Yuiop
相关产品推荐
相关产品推荐

