我注意到
std::vector
容器的交换函数具有与所有其他容器不同的 noexcept 规范。具体来说,如果表达式
std::allocator_traits<Allocator>::propagate_on_container_swap || std::allocator_traits<Allocator>::is_always_equal
为 true,但其他容器要求表达式 std::allocator_traits<Allocator>::is_always_equal
为 true。
既然交换函数的行为是相同的,为什么仅在
std::vector
容器中的 noexcept 规范不同?
您问题中讨论的
noexcept
规范是通过C++17通过N4258引入的。本文从vector
/string
与其他容器(deque
、list
、map
等)的区别在于noexcept
移动分配1规范和swap
功能,总结下面2:
集装箱 | 移动作业 |
|
---|---|---|
/
|
|
|
其他 |
|
|
对于
vector
和string
以外的容器,仅依靠POCMA
来保证移动分配的noexcept
是不够的。某些实现需要使用移动的分配器分配哨兵以实现移出状态,这可能会引发异常。值得注意的是,微软的 std::set
实现有一个投掷移动赋值运算符。论文解释:
[...] 即使使用 POCMA,我们也不能保证
容器是否是基于“某种”节点的(双端队列、列表、关联、无序):如果分配器传播并且移出的对象需要重新分配,并不总是相等(因为我不能偷 LHS 的记忆)。
noexcept
现在,关于
swap
,上述推理不适用,因为我们只需要交换所有内容,包括提到的哨兵对象。因此,不需要重新分配,POCS || IAE
就足够了。 P0177R2中提供了类似的原理:
[...] 不过,这对于交换操作来说不应该是问题,因为预计会交换分配器以及满足不变量的两个数据结构。 [...]
讨论了这些要点后,当前标准似乎可能存在缺陷。一个可能的原因可能是委员会的意图是允许基于节点的容器的
swap
操作根据其移动分配来实现,从而导致与后者相同的 noexcept
规范。不幸的是,N4258 中提到的可能提供进一步见解的讨论是非公开的(来自WG21 Wiki),因此这仍然是推测性的。
为了获得更多见解,我调查了三个主要供应商基于节点的容器的实现,以确定他们为
noexcept
功能提供的 swap
保证。以下是调查结果3:
集装箱 | libc++ | libstdc++ | 微软STL |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
如表中所示,所有这些实现确实为其各自的
noexcept
功能提供了强化的 swap
规范。
1虽然问题中没有直接解决移动分配,但这里提到它是因为它可能会影响
noexcept
操作的swap
规范的设计。POCMA
代表propagate_on_container_move_assignment
,POCS
代表propagate_on_container_swap
,IAE
代表is_always_equal
。为了简洁起见,此处省略了诸如 Compare
规范之类的谓词。