TBB concurrent_unordered_map 的非成员二元比较:operator== 与 operator!= 的规范语义与源码实现
2026/9/14 8:48:29 网站建设 项目流程

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对象相等,当且仅当以下两个条件同时成立:

  1. 两者包含的元素个数相等;
  2. 一个容器中的每个元素,在另一个容器中也都存在。

规范为这两个非成员运算符给出了如下函数签名(与文档原文一致):

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 );
  • 返回值lhsrhs相等返回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_mapvalue_typestd::pair<const Key, T>(见 concurrent_unordered_map.h 中concurrent_unordered_map_traits的定义),因此“元素相同”意味着键值对整体相等——即使键相同,只要映射值不同,容器也不相等。
  • 顺序无关。无序容器的迭代顺序由哈希桶分布决定,两个完全同源的容器迭代顺序也可能不同;规范用“每个元素也都存在”这一集合式语义,而非逐位置比较,正体现了“unordered”的容器特性。
  • 两个操作数必须是同一模板参数组合(相同的KeyTHashKeyEqualAllocator)的concurrent_unordered_map

二、源码实现:运算符定义在共享基类上

规范只描述了concurrent_unordered_map的接口,但从源码结构看,这两个运算符实际上定义在所有无序并发容器的公共基类concurrent_unordered_base上。这意味着concurrent_unordered_multimap复用的是同一套实现——这也解释了为什么 concurrent_unordered_map.h 中concurrent_unordered_mapconcurrent_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

这段实现与规范语义逐条对应,可以拆解为三个步骤:

  1. 自比较短路&lhs == &rhs时直接返回true。规范中“元素个数相等 + 元素互含”对同一对象天然成立,此判断只是零成本优化。
  2. 先比大小lhs.size() != rhs.size()时立即返回false。这正是规范第 1 条“包含相等数量的元素”——先做廉价的计数比较,避免进入昂贵的逐元素扫描。
  3. 再判“元素互含”std::is_permutation(lhs.begin(), lhs.end(), rhs.begin())判定rhs是否为lhs元素序列的某个排列,即rhs中的每个元素都能按value_typeoperator==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()的结果可能与容器实际状态不同。推论:若比较执行的同时其他线程正在并发insertoperator==的结果反映的是比较开始时的快照状态,而非“最终一致”的状态——这与 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)、非成员swapnon_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/tbbtest/conformance下的测试提供了行为验证的入口。

【免费下载链接】moldmold: A Modern Linker 🦠项目地址: https://gitcode.com/GitHub_Trending/mo/mold

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询