oneTBB concurrent_priority_queue 对象生命周期全解析构造、析构、拷贝与赋值语义【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold本文以 oneAPI Threading Building BlocksoneTBB规范文档 construct_destroy_copy.rst 为骨架系统讲解oneapi::tbb::concurrent_priority_queue的全部构造、析构、拷贝/移动语义与赋值接口。读完本文你将掌握该并发容器完整的生命周期管理 API、分配器传播规则、assign与拷贝赋值的差异以及每种操作的并发安全边界可直接用于多线程任务调度、事件优先级处理等实战场景。1. 前置知识类模板签名与类型要求在深入构造函数之前先明确容器的模板签名与成员类型。根据类总览文档 concurrent_priority_queue_cls.rst其声明为namespace oneapi { namespace tbb { template typename T, typename Compare std::lessT, typename Allocator cache_aligned_allocatorT class concurrent_priority_queue { public: using value_type T; using reference T; using const_reference const T; using size_type implementation-defined unsigned integer type; using difference_type implementation-defined signed integer type; using allocator_type Allocator; // ... 构造函数 / 成员函数 / 非成员函数 }; } // namespace tbb } // namespace oneapi在仓库源码 concurrent_priority_queue.h 中实现与规范一致template typename T, typename Compare std::lessT, typename Allocator cache_aligned_allocatorT class concurrent_priority_queue {三个模板参数各有明确约束见类总览文档的 Requirements 一节模板参数默认值必须满足的标准要求T元素类型无Erasable[container.requirements]且不同成员函数可能提出更严格的要求Compare比较谓词std::lessTCompare[alg.sorting]用于定义元素优先级顺序Allocator分配器cache_aligned_allocatorTAllocator[allocator.requirements]默认分配器cache_aligned_allocatorT定义在 cache_aligned_allocator.h它保证每个分配对象的起始地址落在缓存行cache line边界上从而减少多线程访问相邻元素时的伪共享false sharing同时它声明了propagate_on_container_move_assignment std::true_type与is_always_equal std::true_type这两个特性会直接影响下文要讲到的移动赋值与移动构造语义。命名空间说明规范的正式命名空间为oneapi::tbb。仓库同时提供了兼容头 tbb/concurrent_priority_queue.h它仅转发包含oneapi版本头文件因此tbb::concurrent_priority_queue亦可使用。源码实现位于tbb::detail::d1内部命名空间并通过inline namespace v1对外暴露见 concurrent_priority_queue.h。1.1 从源码看容器的内部存储结构要理解构造函数的行为有必要先了解底层数据结构。在 concurrent_priority_queue.h 中实现以std::vectorvalue_type, allocator_type data作为唯一存储并维护三个状态量mark未堆化unheapified元素的起点下标my_sizestd::atomicsize_type当前元素总数my_compare优先级比较谓词。data的内存布局是前缀二叉堆 后缀待堆化元素binary unheapified heap elements ____|_______|____ | | | | v v v v [_|...|_|_|...|_| |...| ] 0 ^ ^ ^ | | |__capacity | |__my_size |__mark即下标[0, mark)是已堆化的二叉堆下标[mark, my_size)是尚未插入堆的元素由push批量写入、在try_pop批量取出时再heapify()合并相关逻辑见handle_operations。这一设计使push与try_pop可以通过聚合器aggregator批量处理、摊销常数时间内完成。这也解释了为什么每一个构造函数都必须调用my_aggregator.initialize_handler(functor{this})——并发操作依赖聚合器分发执行任何一条构造路径都不能遗漏该初始化。2. 空容器构造函数规范文档construct_destroy_copy.rst给出了 5 个构造空容器的重载concurrent_priority_queue(); explicit concurrent_priority_queue( const allocator_type alloc ); explicit concurrent_priority_queue( const Compare compare, const allocator_type alloc ); concurrent_priority_queue( size_type init_capacity, const allocator_type alloc allocator_type() ); concurrent_priority_queue( size_type init_capacity, const Compare compare, const allocator_type alloc allocator_type() );语义要点默认构造构造一个空的concurrent_priority_queue初始容量未指定由实现决定。提供compare时使用该谓词进行优先级比较。提供alloc时使用该分配器分配内存。提供init_capacity时为init_capacity个元素预留容量compare与alloc均带默认实参可省略。2.1 源码对照实现concurrent_priority_queue.h展示了这些重载的细节concurrent_priority_queue() : concurrent_priority_queue(allocator_type{}) {} explicit concurrent_priority_queue( const allocator_type alloc ) : mark(0), my_size(0), my_compare(), data(alloc) { my_aggregator.initialize_handler(functor{this}); } explicit concurrent_priority_queue( const Compare compare, const allocator_type alloc allocator_type() ) : mark(0), my_size(0), my_compare(compare), data(alloc) { my_aggregator.initialize_handler(functor{this}); } explicit concurrent_priority_queue( size_type init_capacity, const allocator_type alloc allocator_type() ) : mark(0), my_size(0), my_compare(), data(alloc) { data.reserve(init_capacity); my_aggregator.initialize_handler(functor{this}); } explicit concurrent_priority_queue( size_type init_capacity, const Compare compare, const allocator_type alloc allocator_type() ) : mark(0), my_size(0), my_compare(compare), data(alloc) { data.reserve(init_capacity); my_aggregator.initialize_handler(functor{this}); }可以观察到几点实现事实默认构造函数委托给allocator_type{}版本保证所有构造路径都执行聚合器初始化init_capacity版本通过data.reserve(init_capacity)预留容量避免后续push频繁扩容mark与my_size均初始化为 0语义上等价于空容器带explicit的构造函数禁止隐式类型转换例如concurrent_priority_queueint q 42;是不合法的必须写concurrent_priority_queueint q(42);。2.2 验证conformance 测试conformance_concurrent_priority_queue.cpp 中的test_ctors_dtor_accessors逐一验证了这些重载// 默认构造size() 0 且 empty() cpq_type cpq1; REQUIRE_MESSAGE(cpq1.size() 0, Failed size test for default ctor); REQUIRE_MESSAGE(cpq1.empty(), Failed empty test for default ctor); // 容量构造cpq2(42) 后 size() 仍为 0 cpq_type cpq2(42); // 比较器构造 / 比较器分配器构造 / 容量比较器 / 容量比较器分配器 cpq_with_compare_type cpq3(l); cpq_with_compare_and_allocator_type cpq4(l, a); cpq_with_compare_type cpq5(42, l); cpq_with_compare_and_allocator_type cpq6(42, l, a);值得注意的是测试中使用的LessA比较器在默认构造时触发断言REQUIRE_MESSAGE(no_assert, Default ctor should not be called)用于证明只要调用方显式传入比较器对象实现就不会额外默认构造比较器——这与 If provided, uses the predicatecompare 的规范语义一致。3. 从元素序列构造范围构造与初始化列表构造规范文档给出 4 个从已有元素序列构造的重载template typename InputIterator concurrent_priority_queue( InputIterator first, InputIterator last, const allocator_type alloc allocator_type() ); template typename InputIterator concurrent_priority_queue( InputIterator first, InputIterator last, const Compare compare, const allocator_type alloc allocator_type() ); concurrent_priority_queue( std::initializer_listvalue_type init, const allocator_type alloc allocator_type() ); concurrent_priority_queue( std::initializer_listvalue_type init, const Compare compare, const allocator_type alloc allocator_type() );语义要点前两个重载构造一个包含半开区间[first, last)全部元素的队列后两个重载等价于concurrent_priority_queue(init.begin(), init.end(), alloc)与concurrent_priority_queue(init.begin(), init.end(), compare, alloc)InputIterator必须满足 ISO C 标准 [input.iterators] 一节对InputIterator的要求提供compare时用于优先级比较提供alloc时用于内存分配。3.1 源码对照一次构建、一次建堆实现concurrent_priority_queue.htemplate typename InputIterator concurrent_priority_queue( InputIterator begin, InputIterator end, const Compare compare, const allocator_type alloc allocator_type() ) : mark(0), my_compare(compare), data(begin, end, alloc) { my_aggregator.initialize_handler(functor{this}); heapify(); my_size.store(data.size(), std::memory_order_relaxed); } template typename InputIterator concurrent_priority_queue( InputIterator begin, InputIterator end, const allocator_type alloc allocator_type() ) : concurrent_priority_queue(begin, end, Compare(), alloc) {} concurrent_priority_queue( std::initializer_listvalue_type init, const Compare compare, const allocator_type alloc allocator_type() ) : concurrent_priority_queue(init.begin(), init.end(), compare, alloc) {} concurrent_priority_queue( std::initializer_listvalue_type init, const allocator_type alloc allocator_type() ) : concurrent_priority_queue(init, Compare(), alloc) {}实现上的关键点初始化列表重载全部委托给范围构造范围构造不带比较器时又委托给Compare()版本——只存在一条真正执行建堆的核心路径data(begin, end, alloc)一次性地用std::vector的迭代器构造拷贝全部元素随后调用heapify()在线性时间内完成建堆heapify的实现见 concurrent_priority_queue.h它逐个将未堆化元素上滤到堆中合适位置最后以std::memory_order_relaxed原子地发布my_size相比先默认构造、再逐个push序列构造避免了push逐次走聚合器与堆化路径建堆成本为 O(n)更适合从一批数据一次性初始化队列。3.2 验证测试用例conformance_concurrent_priority_queue.cpp 对范围构造做了完整断言// 构造 42 个元素后 size() 42非空且元素内容与源序列一致 cpq_type cpq7(v.begin(), v.end()); REQUIRE_MESSAGE(cpq7.size() 42, Failed size test for half-open range ctor); REQUIRE_MESSAGE(!cpq7.empty(), Failed empty test for half-open range test); REQUIRE_MESSAGE(v toVector(cpq7), Failed equality test for half-open range ctor); // 范围 比较器版本 cpq_with_compare_type cpq8(v.begin(), v.end(), l);toVector辅助函数定义于 concurrent_priority_queue_common.h通过不断try_pop再反转结果的方式把队列内容还原为有序序列从而验证构造结果。初始化列表支持则由test_initializer_list覆盖test_initializer_list_supportoneapi::tbb::concurrent_priority_queuechar({1, 2, 3, 4, 5}); test_initializer_list_supportoneapi::tbb::concurrent_priority_queueint({});4. 拷贝构造规范文档定义两个拷贝构造重载concurrent_priority_queue( const concurrent_priority_queue other ); concurrent_priority_queue( const concurrent_priority_queue other, const allocator_type alloc );语义要点两个重载都构造other的副本若未显式提供alloc分配器通过std::allocator_traitsallocator_type::select_on_container_copy_construction(other.get_allocator())获得——即遵循标准分配器特征约定的拷贝构造时选择性传播分配器规则与other并发操作同时执行 push/pop 等时行为未定义——拷贝构造会整体读取other的状态不允许其他线程同时修改它。4.1 源码对照实现concurrent_priority_queue.hconcurrent_priority_queue( const concurrent_priority_queue other ) : mark(other.mark), my_size(other.my_size.load(std::memory_order_relaxed)), my_compare(other.my_compare), data(other.data) { my_aggregator.initialize_handler(functor{this}); } concurrent_priority_queue( const concurrent_priority_queue other, const allocator_type alloc ) : mark(other.mark), my_size(other.my_size.load(std::memory_order_relaxed)), my_compare(other.my_compare), data(other.data, alloc) { my_aggregator.initialize_handler(functor{this}); }注意data(other.data)与data(other.data, alloc)分别对应std::vector的无分配器拷贝构造内部按select_on_container_copy_construction决定分配器与指定分配器拷贝构造两条路径与规范描述一一对应。由于拷贝构造会完整复制data因此要求value_type满足拷贝构造要求可拷贝构造。4.2 验证测试用例conformance_concurrent_priority_queue.cpp 验证拷贝构造后内容与源队列完全一致cpq_type cpq9(cpq7); REQUIRE_MESSAGE(cpq9.size() cpq7.size(), Failed size test for copy ctor); REQUIRE_MESSAGE(cpq9 cpq7, Failed equality test for copy ctor);此外公共测试 concurrent_priority_queue_common.h 中的examine(const QueueType q, ...)也以QueueType q1(q), q2(q);拷贝构造两个副本作为后续并发/串行操作的起点。5. 移动构造规范文档定义两个移动构造重载concurrent_priority_queue( concurrent_priority_queue other ); concurrent_priority_queue( concurrent_priority_queue other, const allocator_type alloc );语义要点使用移动语义构造other的副本移动后other处于**有效但未指定valid, but unspecified**的状态——可以安全析构、重新赋值或重新使用但不能假设其内容若未显式提供alloc分配器通过std::move(other.get_allocator())获得与other并发操作时行为未定义。5.1 源码对照窃取移动 vs 逐元素移动实现concurrent_priority_queue.hconcurrent_priority_queue( concurrent_priority_queue other ) : mark(other.mark), my_size(other.my_size.load(std::memory_order_relaxed)), my_compare(other.my_compare), data(std::move(other.data)) { my_aggregator.initialize_handler(functor{this}); } concurrent_priority_queue( concurrent_priority_queue other, const allocator_type alloc ) : mark(other.mark), my_size(other.my_size.load(std::memory_order_relaxed)), my_compare(other.my_compare), data(std::move(other.data), alloc) { my_aggregator.initialize_handler(functor{this}); }底层依赖std::vector的移动语义由此派生出两种典型行为窃取移动steal move当other的分配器与目标或可传播分配器兼容时data(std::move(other.data))直接接管other的缓冲区不触发任何元素的移动构造/拷贝构造逐元素移动per-element move当移动构造显式传入一个与源分配器不相等的分配器时如测试中特意构造的双内存竞技场场景std::vector只能在新分配器上逐元素移动构造。conformance 测试精确地区分并验证了这两条路径conformance_concurrent_priority_queue.cpp// 窃取移动不应创建任何新元素 container_type dst{std::move(fixture.cpq_src)}; REQUIRE_MESSAGE(previous MoveOperationTracker::special_member_calls(), Steal move ctor should not create any new elements); // 带分配器的窃取移动源分配器相同 fixture_type::cpq_type dst(std::move(fixture.cpq_src), arena_fixture.source_allocator); REQUIRE_MESSAGE(previous MoveOperationTracker::special_member_calls(), ...); // 带不同分配器的移动应为每个元素各调用一次移动构造 fixture_type::cpq_type dst(std::move(fixture.cpq_src), arena_fixture.dst_allocator); REQUIRE_MESSAGE(move_ctor_called_cpq_size_times MoveOperationTracker::special_member_calls(), Per element move ctor should move initialize all new elements);测试通过MoveOperationTracker统计拷贝/移动构造与赋值调用次数从计数上锁定了窃取移动零元素构造、跨分配器移动逐元素构造的实现事实。6. 析构函数~concurrent_priority_queue();语义要点销毁队列调用所有存储元素的析构函数并释放所使用的存储与*this并发操作时行为未定义——析构期间不得有其他线程访问该对象。从源码看concurrent_priority_queue.h 中并未显式声明析构函数这意味着它由编译器隐式生成析构工作全部委托给成员std::vectorvalue_type, allocator_type data完成data的析构会依次调用元素析构并归还分配器内存。规范文档将这一行为显式化保证实现必须满足元素析构 存储释放的契约。7. 赋值运算符规范文档定义 3 个赋值运算符concurrent_priority_queue operator( const concurrent_priority_queue other ); concurrent_priority_queue operator( concurrent_priority_queue other ); concurrent_priority_queue operator( std::initializer_listvalue_type init );7.1 拷贝赋值用other中元素的副本替换*this中的所有元素若std::allocator_traitsallocator_type::propagate_on_container_copy_assignment::value为true则拷贝赋值分配器目标队列的分配器被替换为源分配器的副本与*this或other并发操作时行为未定义返回*this的引用。实现concurrent_priority_queue.hconcurrent_priority_queue operator( const concurrent_priority_queue other ) { if (this ! other) { data other.data; mark other.mark; my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed); } return *this; }实现细节自带自赋值保护this ! other分配器传播语义由std::vector的拷贝赋值隐式承担mark与my_size同步复制其中my_size采用 relaxed 原子存储与size()的 relaxed 原子读concurrent_priority_queue.h对称。7.2 移动赋值用other中的元素移动语义替换*this中的元素移动后other处于有效但未指定状态若std::allocator_traitsallocator_type::propagate_on_container_move_assignment::value为true则移动赋值分配器与*this或other并发操作时行为未定义返回*this的引用。实现concurrent_priority_queue.hconcurrent_priority_queue operator( concurrent_priority_queue other ) { if (this ! other) { // TODO: check if exceptions from std::vector::operator(vector) should be handled separately data std::move(other.data); mark other.mark; my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed); } return *this; }与移动构造同理移动赋值也有窃取与逐元素移动两种形态由分配器是否可传播/相等决定。conformance 测试通过自定义POCMApropagate-on-container-move-assignment特性的分配器分别验证conformance_concurrent_priority_queue.cpp// POCMA true窃取移动赋值不创建新元素 dst std::move(fixture.cpq_src); REQUIRE_MESSAGE(previous MoveOperationTracker::special_member_calls(), ...); // POCMA false逐元素移动赋值 dst std::move(fixture.cpq_src); REQUIRE_MESSAGE(move_ctor_called_cpq_size_times MoveOperationTracker::special_member_calls(), Per element move assignment should move initialize all new elements);由于默认分配器cache_aligned_allocator声明了propagate_on_container_move_assignment std::true_type且is_always_equal std::true_type默认场景下移动赋值/移动构造都是零拷贝的窃取式操作。7.3 初始化列表赋值用init中的元素替换*this中的所有元素与*this并发操作时行为未定义返回*this的引用。实现concurrent_priority_queue.h直接委托给assignconcurrent_priority_queue operator( std::initializer_listvalue_type init ) { assign(init.begin(), init.end()); return *this; }7.4 验证测试用例conformance_concurrent_priority_queue.cpp 验证赋值后内容、大小与相等性qo q; REQUIRE_MESSAGE(qo.size() 42, Failed assignment size test); REQUIRE_MESSAGE(v toVector(qo), Failed assignment equality test); REQUIRE_MESSAGE(qo q, Failed assignment equality test);公共测试 concurrent_priority_queue_common.h 还验证了初始化列表赋值queue_type q3; q3 {vec[0], vec[1], vec[2]};8. assign 成员函数规范文档定义两个assign重载template typename InputIterator void assign( InputIterator first, InputIterator last ); void assign( std::initializer_listvalue_type init );语义要点范围版本用半开区间[first, last)的元素替换*this中的全部元素初始化列表版本等价于assign(init.begin(), init.end())InputIterator必须满足 [input.iterators] 要求与*this并发操作时行为未定义。8.1 源码对照实现concurrent_priority_queue.htemplate typename InputIterator void assign( InputIterator begin, InputIterator end ) { data.assign(begin, end); mark 0; my_size.store(data.size(), std::memory_order_relaxed); heapify(); } void assign( std::initializer_listvalue_type init ) { assign(init.begin(), init.end()); }实现事实data.assign(begin, end)整体替换底层向量随后重置mark并重新heapify()最后原子更新my_size。也就是说assign与拷贝赋值有两个关键区别参数形态不同assign接收的是迭代器区间或初始化列表内容拷贝赋值接收的是另一个concurrent_priority_queue对象容器分配器语义不同assign不涉及分配器传播propagate_on_container_*_assignment规则只适用于容器到容器的赋值它只负责替换元素内容分配器保持目标队列自身的分配器不变。8.2 验证测试用例conformance_concurrent_priority_queue.cppcpq_type assigned_q; assigned_q.assign(v.begin(), v.end()); REQUIRE_MESSAGE(assigned_q.size() 42, Failed assign size test); REQUIRE_MESSAGE(v toVector(assigned_q), Failed assign equality test);公共测试 concurrent_priority_queue_common.h 也在空队列上调用q1.assign(vec.begin(), vec.end())后立即检验排序结果。9. 并发安全边界总览本规范文档反复强调每种生命周期操作的未定义行为情形本质上是任何会整体读取/改写容器状态的构造、赋值、析构操作都不得与针对同一对象或其他对象的并发 push/pop/emplace 同时发生。汇总如下操作与谁并发操作属于 UB拷贝构造other移动构造other析构函数*this拷贝赋值*this与other移动赋值*this与other初始化列表赋值*thisassign两个重载*this与之形成对照的是push、emplace、try_pop是文档明确允许多线程并发执行的安全修改操作见类总览文档与 safe_modifiers.rst 说明。因此在实战中对象的出生构造—复制/转移—销毁阶段必须与其他线程对队列的访问互斥通常由外围同步原语锁、生命周期管理框架保证。10. 延伸CTAD 与其它非成员接口虽然构造与拷贝是本文主题但规范在类总览中同时声明了 C17 类模板实参推导CTAD指引这对从序列构造的书写方式有直接影响。实现中的推导指引见 concurrent_priority_queue.h#if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT template typename It, typename Comp std::lessiterator_value_tIt, typename Alloc tbb::cache_aligned_allocatoriterator_value_tIt, ... concurrent_priority_queue( It, It, Comp Comp(), Alloc Alloc() ) - concurrent_priority_queueiterator_value_tIt, Comp, Alloc; template typename It, typename Alloc, ... concurrent_priority_queue( It, It, Alloc ) - concurrent_priority_queueiterator_value_tIt, std::lessiterator_value_tIt, Alloc; template typename T, typename Comp std::lessT, typename Alloc tbb::cache_aligned_allocatorT, ... concurrent_priority_queue( std::initializer_listT, Comp Comp(), Alloc Alloc() ) - concurrent_priority_queueT, Comp, Alloc; template typename T, typename Alloc, ... concurrent_priority_queue( std::initializer_listT, Alloc ) - concurrent_priority_queueT, std::lessT, Alloc; #endif借助 CTAD无需显式写出模板实参即可从迭代器区间或初始化列表推导元素类型例如concurrent_priority_queue q(vec.begin(), vec.end());。conformance 测试的TestDeductionGuidesconformance_concurrent_priority_queue.cpp用static_assert逐一验证了迭代器区间、分配器、比较器、初始化列表等各组合下的类型推导结果。此外规范还提供与生命周期相关的非成员接口定义于 non_member_swap.rst 与 non_member_binary_comparisons.rsttemplate typename T, typename Compare, typename Allocator void swap( concurrent_priority_queueT, Compare, Allocator lhs, concurrent_priority_queueT, Compare, Allocator rhs ); template typename T, typename Compare, typename Allocator bool operator( const concurrent_priority_queueT, Compare, Allocator lhs, const concurrent_priority_queueT, Compare, Allocator rhs ); template typename T, typename Compare, typename Allocator bool operator!( const concurrent_priority_queueT, Compare, Allocator lhs, const concurrent_priority_queueT, Compare, Allocator rhs );拷贝构造、拷贝赋值、assign等接口的正确性正是通过这些/!比较在测试中如REQUIRE_MESSAGE(qo q, ...)得到验证的。swap的成员实现见 concurrent_priority_queue.h它交换底层data、mark与原子my_size属于影响整个容器的非线程安全操作。11. 小结与实战建议concurrent_priority_queue的生命周期 API 完整对齐标准容器的惯例并针对并发场景做了明确约束。综合规范文档 construct_destroy_copy.rst 与仓库源码可以提炼出以下实战要点初始化优先用序列构造从已有数据集初始化队列时范围/初始化列表构造会直接建堆O(n)比空构造 逐个 push更高效移动语义默认是零拷贝的默认cache_aligned_allocator满足可传播且等值移动构造/移动赋值表现为窃取式操作可放心用于传递大队列跨分配器移动会退化为逐元素移动若显式传入不同分配器或使用不可传播分配器移动构造/赋值将逐元素搬移成本与元素数量线性相关区分assign与拷贝赋值assign只替换内容、不传播分配器拷贝赋值在propagate_on_container_copy_assignment为真时还会替换目标分配器严格遵守并发边界构造、拷贝、移动、析构、赋值、assign均不得与相关对象的并发修改同时进行只有push/emplace/try_pop支持并发合理利用 CTAD 与比较接口C17 下可省略模板实参/!比较既可用于测试断言也可用于生命周期操作后的正确性校验。若需进一步探索相邻语义可继续阅读同目录下的 deduction_guides.rst、size_and_capacity.rst、safe_modifiers.rst 与 unsafe_modifiers.rst并对照测试 conformance_concurrent_priority_queue.cpp 与 test_concurrent_priority_queue.cpp 中的完整用例。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考