oneTBB concurrent_vector 非成员二元比较运算(operator== / operator!=)规范与源码实现解析
【免费下载链接】moldmold: A Modern Linker 🦠项目地址: https://gitcode.com/GitHub_Trending/mo/mold
本文围绕 oneTBB 规范文档中的 “Non-member binary comparisons” 一节展开,讲解concurrent_vector容器两个非成员比较运算符(operator==与operator!=)的相等性语义、函数签名与返回值约定,并结合当前仓库中 oneTBB 的实际头文件实现(std::equal逐元素比对、C++20 三路比较的条件编译分支)说明其底层行为与适用前提,帮助读者在多线程并行容器场景下正确、高效地使用这些比较接口。
1. 文档定位:concurrent_vector 规范中的比较运算一节
在当前仓库中,oneTBB 的规范文档按容器成员拆分为多个 RST 小节。本主题对应 non_member_binary_comparisons.rst,与同目录下的 non_member_lexicographical_comparisons.rst(字典序比较operator<等)、non_member_swap.rst(swap非成员函数)等章节共同构成concurrent_vector的完整非成员接口规范。
concurrent_vector本身是 oneTBB 提供的可多线程并发追加元素的向量容器,其元素访问、扩容与并发语义可参见同目录的 iterators.rst、concurrent_growth.rst 等规范小节。本节讨论的二元比较运算正是建立在这套迭代器与容量语义之上的“容器级整体比较”。
2. 相等性语义:两个 concurrent_vector 何时相等
规范文档给出的相等性定义是逐位置(element-wise)的,两个concurrent_vector对象相等当且仅当同时满足两个条件:
- 它们包含相同数量的元素(an equal number of elements);
- 相同位置上的元素两两相等(the elements on the same positions are equal)。
这与std::vector的相等语义完全一致,即“同长度 + 逐元素等价”,而不是比较底层内存布局、容量(capacity)或分配器状态。也就是说,即便两个向量分别由不同分配器配置创建、内部扩容历史不同,只要size()相同且按下标 0..n-1 逐元素==成立,二者在比较运算符视角下就是相等的。
3. 函数签名与返回值约定
规范文档为两个运算符给出的完整签名与返回值说明如下:
3.1 operator==
template <typename T, typename Allocator> bool operator==( const concurrent_vector<T, Allocator>& lhs, const concurrent_vector<T, Allocator>& rhs );Returns: 若lhs等于rhs(按第 2 节的相等性定义)返回true,否则返回false。
3.2 operator!=
template <typename T, typename Allocator> bool operator!=( const concurrent_vector<T, Allocator>& lhs, const concurrent_vector<T, Allocator>& rhs );Returns: 若lhs不等于rhs返回true,否则返回false。
两个运算符均为非成员函数模板,参数以const左值引用传入,不修改任何操作数。由于比较只依赖size()与迭代器遍历,因此对const容器对象调用同样成立——这一点可由实现中使用的begin()/end()的const重载保证(见第 4 节)。
4. 源码级实现:size 短路 + std::equal 逐元素比对
规范签名在仓库中的实际实现位于 concurrent_vector.h(旧命名空间头文件 include/tbb/concurrent_vector.h 仅是一行转发到该 oneapi 头文件)。核心实现为:
template <typename T, typename Allocator> bool operator==(const concurrent_vector<T, Allocator> &lhs, const concurrent_vector<T, Allocator> &rhs) { return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); }从这段实现可以读出几个关键行为:
- 长度先行短路:先用
lhs.size() == rhs.size()做前置判断,长度不等时直接返回false,不进入任何元素访问。这是对规范第 2 节第一条“元素数量相等”的直接落地,也意味着不等长的比较开销为 O(1)。 - std::equal 完成逐元素比较:长度相等时调用
std::equal以[begin, end)区间遍历lhs,与rhs从begin()起对应元素逐一==。复杂度为 O(n),其中 n 为共同长度。注意这里没有做“同对象快速返回”之类的特判,比较两个完全相同的局部变量也会走完整遍历。 - const 语义天然满足:
operator==内部使用lhs.begin()/rhs.begin(),而concurrent_vector为begin()/end()提供了const重载(返回const_iterator,见 concurrent_vector.h),因此对const concurrent_vector对象比较可以正常编译。
operator!=的实现则是纯粹的取反(concurrent_vector.h):
#if !__TBB_CPP20_COMPARISONS_PRESENT template <typename T, typename Allocator> bool operator!=(const concurrent_vector<T, Allocator> &lhs, const concurrent_vector<T, Allocator> &rhs) { return !(lhs == rhs); } #endif // !__TBB_CPP20_COMPARISONS_PRESENT规范中“trueiflhsis not equal torhs”的返回约定由此得到保证:operator!=与operator==的结果严格互补,不存在第三态。
5. 一个值得注意的实现细节:C++20 下的条件编译分支
上面的operator!=被#if !__TBB_CPP20_COMPARISONS_PRESENT包裹——在 C++20 比较运算符可用(即编译器支持<=>相关语义)时,oneTBB不再手写operator!=,而是依赖标准库从operator<=>合成的operator!=。对应的三路比较运算符定义紧随其后(concurrent_vector.h):
#if __TBB_CPP20_COMPARISONS_PRESENT && __TBB_CPP20_CONCEPTS_PRESENT template <typename T, typename Allocator> tbb::detail::synthesized_three_way_result<typename concurrent_vector<T, Allocator>::value_type> operator<=>(const concurrent_vector<T, Allocator> &lhs, const concurrent_vector<T, Allocator> &rhs) { return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), tbb::detail::synthesized_three_way_comparator{}); }这说明两点适用前提:
- 在使用 C++20 及以上特性编译时,
operator<、operator>、operator<=、operator>=、operator!=等均由operator<=>派生,比较走字典序(lexicographical)三向比较路径; - 在旧标准模式下,则回退到规范文档描述的显式
operator==/operator!=二元比较路径。
无论哪条路径,“相等”的最终判定口径都与规范一致:长度相同且逐元素相等。读者在实际工程中应留意自己项目的 C++ 标准版本,以判断实际生效的是哪组运算符。
6. 相邻概念辨析:容器比较 vs 迭代器比较
阅读源码时容易与另一个operator==混淆的是迭代器级的相等比较(concurrent_vector.h):
template <typename Vector, typename T, typename U> bool operator==( const vector_iterator<Vector, T>& i, const vector_iterator<Vector, U>& j ) { return i.my_vector == j.my_vector && i.my_index == j.my_index; }两者的区别必须分清:
| 维度 | 容器级operator== | 迭代器级operator== |
|---|---|---|
| 比较对象 | 两个concurrent_vector的整体内容 | 两个迭代器的指向 |
| 判定条件 | size()相等且逐元素== | 指向同一容器对象且下标(index)相同 |
| 时间复杂度 | O(n)(长度不等时 O(1) 短路) | O(1) |
| 用途 | 验证两个容器是否携带相同数据 | 循环终止条件(it != vec.end()) |
特别地,concurrent_vector的迭代器比较检查my_vector(容器指针)是否相同,即只有同一容器内的两个迭代器才可能相等;跨容器的迭代器比较结果为false。这与容器级比较“跨对象比较内容”的语义形成鲜明对照。
7. 实战用法示例
结合规范语义与上述实现,典型用法如下:
#include <oneapi/tbb/concurrent_vector.h> #include <cstdint> int main() { tbb::concurrent_vector<int> a, b; a.reserve(4); b.reserve(4); for (int i = 0; i < 4; ++i) { a.push_back(i); // 顺序追加 b.push_back(i * 2); // 不同内容 } tbb::concurrent_vector<int> c(a.begin(), a.end()); // 区间构造副本 bool same_ac = (a == c); // true:同长度且逐元素相等 bool diff_ab = (a != b); // true:逐元素不等 a.push_back(99); // 长度变为 5,内容仍含 b 没有的差异 bool diff_len = (a != c); // true a.erase(a.end() - 1); // 回到 4 个元素 bool same_again = (a == c); // true }由实现可确认的行为要点:
- 比较是只读操作,不要求容器处于空闲状态;规范未对比较期间的并发写入做额外同步承诺,因此在多线程追加元素的窗口期调用比较,结果应理解为某一次可见长度下的一致快照读取(具体可见性行为建议以 concurrent_growth.rst 中的并发语义为准)。
- 若只想判断“是否为同一对象”或“是否为空”,不要依赖
==:长度不等时 O(1) 短路很快,但空容器之间比较仍会直接走长度判断返回true,成本很低;真正昂贵的是等长容器间的 O(n) 全遍历。 - 元素类型
T必须自身支持operator==(std::equal的要求);若使用 C++20 三向比较路径,则元素还需要支持<=>合成比较,否则operator<=>不可用。
8. 参考路径汇总
| 内容 | 路径 |
|---|---|
| 规范原文(本节主题) | non_member_binary_comparisons.rst |
| 实现头文件(operator== / operator!= / operator<=>) | oneapi/tbb/concurrent_vector.h |
| 旧命名空间转发头文件 | include/tbb/concurrent_vector.h |
| 迭代器实现(含迭代器 operator==) | oneapi/tbb/concurrent_vector.h |
| 关联规范:字典序比较 | non_member_lexicographical_comparisons.rst |
| 关联规范:swap 非成员函数 | non_member_swap.rst |
总结:concurrent_vector的非成员二元比较在规范层面保持了与标准容器一致的“同长度 + 逐元素相等”语义;在实现层面以“长度短路 +std::equal”的朴素而可靠的策略完成,operator!=严格取反operator==,并在 C++20 环境下收敛到operator<=>派生的比较体系。理解这套实现与条件编译分支,可以避免在跨标准版本工程中误判可用运算符,也便于正确评估比较操作的开销。
【免费下载链接】moldmold: A Modern Linker 🦠项目地址: https://gitcode.com/GitHub_Trending/mo/mold
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考