【问题标题】:What is the exception safety guarantee of vector::insert?vector::insert 的异常安全保证是什么?
【发布时间】:2013-11-01 04:24:53
【问题描述】:

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

【问题讨论】:

  • 这很大程度上取决于容器中存储的类型
  • @David:你是对的。我对它没有 nothrow 移动的情况感兴趣 - 例如只扔副本。

标签: c++


【解决方案1】:

一般来说,insert 的单元素形式对任何容器都有很强的例外保证,根据 [container.requirements.general]/10,但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是类型(const)T和@987654346的表达式@ 是容器的值类型。这是一个参数的就地构造(复制或移动构造)。

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

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

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

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

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


我将异常规范解释如下:

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

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

关于“无影响”(-> 强保证)的其他评论应用于分配器操作 (construct)。分配器操作显然不必是 noexcept。因为您可以为construct 提供不使用值类型的复制和移动ctor 的非默认分配器,所以insert 必须提供强大的异常保证,即使对于construct 分配器操作也是如此。例如,如果分配器的 construct 不是 noexcept,则在调整大小期间,元素不能移动构造,但必须复制。

移动和复制分配必须是除了强有力的保证之外,因为元素可能需要为insert 移动;由于算法中创建了本地对象,copy- 和 move-ctor 可能需要为 noexcept 以提供强保证。

【讨论】:

  • 关于你的旁白:如果你确实想在容器内部插入一些元素,你会怎么做?
  • @Mehrdad 使用单元素表单?至少,it's required to work.
【解决方案2】:

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

如果除了T 的复制构造函数、移动构造函数、赋值运算符或移动赋值运算符或任何InputIterator 操作引发异常,则没有任何影响。如果非CopyInsertableT的移动构造函数抛出异常,则效果未指定。

【讨论】:

  • there are no effects 是什么意思?向量中已分配的元素会发生什么情况?
  • @RedX:表示没有效果;一切都恢复到初始状态,仿佛什么都没发生。如果已经分配了元素,那么异常肯定是由“T的复制构造函数、移动构造函数、赋值运算符或移动赋值运算符或任何InputIterator操作”引发的,在这种情况下没有这样的保证.
  • 那么这是否意味着 T 的复制构造函数抛出的基本保证?
  • @DeadMG:我想是的。你的解释可能和我一样好。
【解决方案3】:

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

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

基本保证:组件的不变量被保留,没有资源泄漏。 强有力的保证:操作要么成功完成,要么抛出异常,使程序状态完全保持在操作开始之前的状态。 不抛出保证:操作不会抛出异常。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-24
    • 1970-01-01
    • 2021-03-24
    • 2017-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多