TBB concurrent_unordered_map 的非成员二元比较:operator== 与 operator!= 的规范语义与源码实现
【免费下载链接】moldmold: A Modern Linker 🦠项目地址: https://gitcode.com/GitHub_Trending/mo/mold
本文聚焦 Intel oneAPI TBB(本仓库内嵌于 third-party/tbb 子目录)中concurrent_unordered_map容器的非成员二元比较(Non-member binary comparisons),即规范文档 non_member_binary_comparisons.rst 所定义的operator==与operator!=。读完本文,你将掌握:无序容器相等性的判定条件、这两个运算符在 TBB 源码中的真实实现位置与调用链(std::is_permutation+size())、C++20 合成比较带来的实现差异,以及并发环境下执行比较时的注意事项与验证方式。
一、规范定义:什么情况下两个容器“相等”
规范文档给出的核心语义非常简洁——两个concurrent_unordered_map对象相等,当且仅当以下两个条件同时成立:
- 两者包含的元素个数相等;
- 一个容器中的每个元素,在另一个容器中也都存在。
规范为这两个非成员运算符给出了如下函数签名(与文档原文一致):
template <typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator> bool operator==( const concurrent_unordered_map<Key, T, Hash, KeyEqual, Allocator>& lhs, const concurrent_unordered_map<Key, T, Hash, KeyEqual, Allocator>& rhs );- 返回值:
lhs与rhs相等返回true,否则返回false。
template <typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator> bool operator!=( const concurrent_unordered_map<Key, T, Hash, KeyEqual, Allocator>& lhs, const concurrent_unordered_map<Key, T, Hash, KeyEqual, Allocator>& rhs );operator!=与!(lhs == rhs)等价:不相等返回true,否则返回false。
这里有两点值得注意:
- 相等性由 key 与 value 共同决定。
concurrent_unordered_map的value_type是std::pair<const Key, T>(见 concurrent_unordered_map.h 中concurrent_unordered_map_traits的定义),因此“元素相同”意味着键值对整体相等——即使键相同,只要映射值不同,容器也不相等。 - 顺序无关。无序容器的迭代顺序由哈希桶分布决定,两个完全同源的容器迭代顺序也可能不同;规范用“每个元素也都存在”这一集合式语义,而非逐位置比较,正体现了“unordered”的容器特性。
- 两个操作数必须是同一模板参数组合(相同的
Key、T、Hash、KeyEqual、Allocator)的concurrent_unordered_map。
二、源码实现:运算符定义在共享基类上
规范只描述了concurrent_unordered_map的接口,但从源码结构看,这两个运算符实际上定义在所有无序并发容器的公共基类concurrent_unordered_base上。这意味着concurrent_unordered_multimap复用的是同一套实现——这也解释了为什么 concurrent_unordered_map.h 中concurrent_unordered_map与concurrent_unordered_multimap两个类体内都没有成员operator==/operator!=,真正的定义位于 _concurrent_unordered_base.h:
template <typename Traits> bool operator==( const concurrent_unordered_base<Traits>& lhs, const concurrent_unordered_base<Traits>& rhs ) { if (&lhs == &rhs) { return true; } if (lhs.size() != rhs.size()) { return false; } #if _MSC_VER // Passing "unchecked" iterators to std::permutation with 3 parameters // causes compiler warnings. // The workaround is to use overload with 4 parameters, which is // available since C++14 - minimally supported version on MSVC return std::is_permutation(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); #else return std::is_permutation(lhs.begin(), lhs.end(), rhs.begin()); #endif } #if !__TBB_CPP20_COMPARISONS_PRESENT template <typename Traits> bool operator!=( const concurrent_unordered_base<Traits>& lhs, const concurrent_unordered_base<Traits>& rhs ) { return !(lhs == rhs); } #endif这段实现与规范语义逐条对应,可以拆解为三个步骤:
- 自比较短路:
&lhs == &rhs时直接返回true。规范中“元素个数相等 + 元素互含”对同一对象天然成立,此判断只是零成本优化。 - 先比大小:
lhs.size() != rhs.size()时立即返回false。这正是规范第 1 条“包含相等数量的元素”——先做廉价的计数比较,避免进入昂贵的逐元素扫描。 - 再判“元素互含”:
std::is_permutation(lhs.begin(), lhs.end(), rhs.begin())判定rhs是否为lhs元素序列的某个排列,即rhs中的每个元素都能按value_type的operator==在lhs中找到匹配,反之亦然。这恰好实现了规范第 2 条,并且天然不受迭代顺序影响。
细节说明:
is_permutation的参数形式:在 MSVC 上使用四参数重载(显式传入第四组迭代器)是为了规避对“unchecked”迭代器使用三参数重载时的编译器告警,注释中说明四参数重载自 C++14 起可用,而 C++14 是 MSVC 的最低支持版本。两种重载语义一致。size()是并发感知的。基类中size()的实现是my_size.load(std::memory_order_relaxed)(见 _concurrent_unordered_base.h),即从一个原子计数器松弛读取。TBB 规范在 Size and capacity 章节(size_and_capacity.rst)中明确指出:存在并发插入时,size()的结果可能与容器实际状态不同。推论:若比较执行的同时其他线程正在并发insert,operator==的结果反映的是比较开始时的快照状态,而非“最终一致”的状态——这与 TBB 其他接口的并发语义一脉相承,编写测试或断言时应避免在并发写入进行中直接比较容器。- 对
concurrent_unordered_multimap的适用性:基类通过Traits模板参数区分 map(allow_multimapping = false)与 multimap(true,见 concurrent_unordered_map.h 的concurrent_unordered_map_traits),但比较运算符对两者一视同仁。std::is_permutation对重复元素按重数匹配,因此 multimap 中“同一键两个不同值”与“同一键重复两次”会被区分开,与std::unordered_multimap的相等性语义保持一致。
三、C++20 合成比较:operator!=为何有条件编译
注意源码中operator!=被#if !__TBB_CPP20_COMPARISONS_PRESENT包裹,而operator==始终显式定义。这个宏在 _config.h 中判定:
#if defined(__cpp_impl_three_way_comparison) && defined(__cpp_lib_three_way_comparison) #define __TBB_CPP20_COMPARISONS_PRESENT ((__cpp_impl_three_way_comparison >= 201907L) && (__cpp_lib_three_way_comparison >= 201907L)) #else #define __TBB_CPP20_COMPARISONS_PRESENT 0 #endif其含义是:
- C++17 及更早:编译器不会为
operator==自动合成operator!=,所以必须手写return !(lhs == rhs);,这正是规范中“operator!=与!(lhs == rhs)等价”的落地方式。 - C++20 起(检测到
__cpp_impl_three_way_comparison与__cpp_lib_three_way_comparison特性宏):编译器会为显式定义的operator==自动合成operator!=(语义即!(a == b))。若再手写一份,反而可能与合成版本产生歧义或重复定义,因此源码选择在 C++20 环境下省略手写版本。
同样的模式也出现在 TBB 的其他容器中,例如 concurrent_hash_map.h 与 concurrent_queue.h 中的operator!=都受同一宏保护,属于 TBB 全库统一的实现约定。
四、使用示例与验证途径
典型用法
#include "oneapi/tbb/concurrent_unordered_map.h" tbb::concurrent_unordered_map<int, int> a; a.insert({1, 10}); a.insert({2, 20}); tbb::concurrent_unordered_map<int, int> b = a; // 拷贝构造 assert(a == b); // 元素相同,相等 assert(!(a != b)); // 与 !(a == b) 等价 a[3] = 30; // operator[] 不存在键时会插入,见 concurrent_unordered_map.h assert(a != b); // 元素个数/内容不同 assert(!(a == b));要点复述:
- 键值对整体参与比较:
{1, 10}与{1, 11}被视为不同元素,因此即使键集合相同、仅映射值不同,a == b也为false。 - 迭代顺序不影响结果:实现走
std::is_permutation,桶分布不同导致的顺序差异不会被误判为不相等。
相关测试与规范脉络
- 该文档位于 TBB 规范
concurrent_unordered_map类参考手册的同级章节目录 concurrent_unordered_map_cls/ 中,与构造/复制(construction_destruction_copying.rst)、查找(lookup.rst)、观察器(observers.rst)、非成员swap(non_member_swap.rst)等章节并列,共同构成该容器的完整 API 参考。 - 功能测试入口在 test_concurrent_unordered_map.cpp:测试用
tbb::concurrent_unordered_map<int, int, std::hash<int>, std::equal_to<int>, ...>等类型组合(含degenerate_hash退化哈希场景)覆盖 map 与 multimap 的行为;同目录的 conformance_concurrent_unordered_map.cpp 则属于 C++ 标准一致性(conformance)测试集。 - 非成员
swap与比较运算符一样,是基类模板参数的函数(参见 non_member_swap.rst),在 concurrent_unordered_map.h 中通过lhs.swap(rhs)转发到成员swap。
五、小结
| 项目 | 规范描述 | 源码实现要点 |
|---|---|---|
operator== | 元素个数相等且元素互含 | 地址相等短路 →size()原子读取比对 →std::is_permutation判定元素集合相等,定义于concurrent_unordered_base |
operator!= | 与!(lhs == rhs)等价 | C++20 下由编译器合成;C++17 下显式定义,受__TBB_CPP20_COMPARISONS_PRESENT宏保护 |
| 顺序敏感性 | 无序容器,顺序无关 | is_permutation天然容忍迭代顺序差异 |
| 并发语义 | 规范未单独说明 | size()为松弛原子读取,并发插入进行中时比较结果可能滞后于实际状态 |
对使用者而言,这条 API 的规范语义、实际实现与验证路径在本仓库中形成了完整闭环:规范文档(non_member_binary_comparisons.rst)定义“相等”的判定条件,基类头文件(_concurrent_unordered_base.h)给出与之一一对应的三段式实现,配置头文件(_config.h)解释了 C++20 下的条件编译差异,而test/tbb与test/conformance下的测试提供了行为验证的入口。
【免费下载链接】moldmold: A Modern Linker 🦠项目地址: https://gitcode.com/GitHub_Trending/mo/mold
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考