向量::插入的异常安全保证是什么?

What is the exception safety guarantee of vector::insert?

本文关键字:是什么 异常 插入 向量 安全      更新时间:2023-10-16

我想知道,std::vector::insert的异常安全保证到底是什么?我对这个函数的单参数和范围重载都感兴趣。

通常,根据 [container.requirements.general]/10,单元素形式的 insert 对任何容器都有很强的异常保证,但vector::insert是此规则的例外:

[vector.modifiers]/1 适用于vector::insert ; 这里的InputIterator是指插入范围的insert的重载。

如果不是由复制构造函数、移动构造函数、赋值运算符或移动赋值运算符引发的,则T或任何InputIterator操作引发异常,则不会产生任何影响。如果非CopyInsertable T的移动构造函数抛出异常,则未指定效果。


vector 是一个分配器感知容器,这意味着它使用分配器来分配内存构造其元素 [container.requirements.general]/3

对于受此子句影响的声明allocator_type的组件,存储在这些组件中的对象应使用 allocator_traits<allocator_type>::construct 函数构造并使用 allocator_traits<allocator_type>::destroy 函数销毁。仅为容器的元素类型调用这些函数,而不为容器使用的内部类型调用这些函数。

我认为这意味着本地对象(不是容器的元素)可以在不使用分配器的情况下创建(例如用于复制和交换)。否则,对值类型的 ctor 的要求将毫无意义;分配器的 construct 函数可能具有与值类型的 CTOR 不同的异常保证。


CopyInsertable在 [container.requirements.general]/13 中指定,要求

allocator_traits<A>::construct(m, p, v);

格式正确;其中A是分配器类型,mA类型,p是指向T的指针,v是类型(constT的表达式,T是容器的值类型。这是一个参数的就地构造(复制或移动构造)。

同样,指定了 MoveConstructible,但v(始终)是类型 T 的右值。 对于零个或多个参数,EmplaceConstructible遵循相同的形式,而不是v

序列容器的insert函数对其各种形式的值类型施加了不同的要求[sequence.reqmts];这里包括对vector的其他要求:

    对于类型
  • 为(const T)的左值的单个参数,对于插入N个副本的形式,TCopyInsertableCopyAssignable
  • 对于类型为
  • T 的右值的单个参数,TMoveInsertableMoveAssignable
  • 对于范围形式,应从取消引用的迭代器 (*) 中EmplaceConstructible T;此外,如果范围的迭代器不是前向迭代器,则MoveInsertableMoveAssignable

(*) 注意:如果必须调整容器的大小以进行插入(例如,此类迭代器的*i不是值类型),则仅从取消引用的迭代器中EmplaceConstructible是不够的。规范可能要求范围形式继承单元素形式的要求,即MoveAssignableCopyAssignable

附带备注:insert的范围形式要求两个迭代器不指向要插入它们的容器。


我将异常规范解释如下:

vector::insert的异常规范中关于CopyInsertable的附加语句可能区分了基本保证和无保证:容器的 dtor 通常需要调用所有元素的 dtor 并释放所有内存(在一般容器需求中)。也就是说,除非行为未指定/未定义,否则基本保证成立。

为什么有将CopyInsertable和移动 ctor 结合起来的要求(而不是用右值allocator::construct),我不知道。move-ctor 仅直接用于不是容器元素的对象(间接可能通过 allocator::construct )。

关于"无影响"(->强保证)的其他注释不适用于分配器操作(construct)。分配器操作显然不必是例外。由于您可以提供一个非默认分配器,该分配器不使用值类型的复制和移动 ctor 进行construct,因此即使对于construct分配器操作,insert也必须提供强大的异常保证。例如,如果分配器的construct不是 noex,但在调整大小期间,元素不能移动构造,而必须复制。

移动

和复制赋值必须为"无",因为元素可能需要移动insert;复制和移动 ctor 可能需要为"否",因为算法中创建了本地对象,因此需要"强保证"。

确切的保证在 C++11 23.3.6.5 中给出:

如果引发异常不是由复制构造函数、移动构造函数、赋值运算符或移动赋值运算符T或任何InputIterator操作引发的,则不会产生任何影响。如果非CopyInsertable T的移动构造函数抛出异常,则未指定效果。

如果 insert 方法在列表末尾插入单个元素并且不需要分配任何内存,则提供强大的异常保证。

如果它必须

添加多个元素,或者必须分配内存,它提供了一个基本的异常保证。 Boost对异常保证有很好的描述。

基本保证:保留组件的不变量,不泄露任何资源。 强有力的保证:操作已成功完成或引发异常,使程序状态与操作开始之前完全相同。 无抛出保证:操作不会引发异常。

这意味着在异常之后,您知道该vector将可用,但它可能没有您插入的所有数据。 所有成功插入的对象都将被完全构造。