fill-insert()-复制构造函数和复制赋值noexcept状态

fill insert() - copy constructor and copy assignment noexcept status?

本文关键字:复制 赋值 noexcept 状态 构造函数 fill-insert      更新时间:2023-10-16
  1. STL容器元素是否需要具有noexcept复制构造函数和复制赋值运算符?如果可能的话,请提供参考资料
  2. 如果不是,在多次插入期间发生异常时,STL容器的状态是什么,例如在填充插入期间

当试图编写一个允许拦截/否决修改的通用包装时,就会出现这个问题。我能想到的任何实现都可能改变底层容器的语义,除非专门针对每种容器类型(这实际上不是一个选项(。

例如,std::vector具有填充插入:

void insert (iterator position, size_type n, const value_type& val);

这要求value_type既是CopyInsertable又是CopyAssignable。请注意,它不要求值类型为DefaultConstructable

Edit 3Stroustrup自己(第956页的表(指出多元素插入应该对所有向量、deque、list和map都有强保证。这意味着整个标准库操作要么成功,要么失败。

Edit 4但是,只有当相关操作(在本例中为复制构造函数(本身没有抛出异常时,才适用该保证,这正是我的问题。

据我所知,这留下了两种基本的实现方法:

  1. 为新元素创建条目,并复制分配val。只有当可以通过复制容器中的现有元素来创建伪元素时,或者当value_typeDefaultConstructible时(这不是必需的(,这才有效
  2. 将构造元素一个接一个地直接复制到容器中它们各自的位置。这似乎或多或少是规范化的实现

编辑2:我不会把这个称为未定义行为,因为这个词似乎让人们认为未定义为语言运行时/标准

当复制构造函数或复制赋值运算符引发异常时,这两个实现似乎都会使容器留下未知内容(即,不清楚容器在异常后包含哪些元素(

编辑1:请注意,这并不意味着我认为C++运行时存在不良行为,例如内存泄漏或未定义的值。然而,似乎或多或少有未指定容器的内容。特别是,容器中的内容可能已经完全(尽管一直(改变了。

例如,考虑第三种(混合(方法:

  1. 创建模板对象CCD_ 7的CCD_
  2. 将该列表中的元素复制并分配到目标容器中

不同之处在于复制构造函数引发异常时对容器的影响。在这种情况下,如果复制构造函数抛出,容器的内容将保持不变(但当复制赋值运算符抛出时,仍会导致未指定的内容(。当使用指针时(即,当不使用std::vector时(,可能会忽略复制分配,只重新排列指针,从而使操作成为原子写入异常。

至于容器元素上的noexcept:对象是通过allocator_traits<value_type>::construct(ptr, args)创建的,而不是noexcept,我也找不到容器元素大多数都有noexcept复制构造函数/复制赋值运算符的要求(例如std::shared_ptrstd::unique_ptr需要这样(。

请注意,为复制构造和复制分配自动生成的操作要求为noexcept,除非它们被要求调用本身可能引发异常的操作。

这让我很困惑,我确信我错过了一些东西,但我无法找出标准中可能证明我错了的部分。

  1. STL容器元素是否需要有noexcept复制构造函数和复制赋值运算符?如果可能的话,请提供参考资料

不,它们不是,我不能为不存在的需求提供参考,因为它不存在。

如果需要,它会在标准中这样说,但它没有。

一般情况下,元素甚至根本不需要有一个复制构造函数,对于大多数操作来说,一个移动构造函数就足够了。副本分配也是如此。

  1. 如果不是,当在多重插入过程中发生异常时,STL容器的状态是什么,例如在填充插入过程中

它取决于容器,以及您要插入的容器中的位置。

对于基于节点的容器(如列表(,如果一次插入抛出,则已插入的任何元素都将保留在容器中。

对于std::vector,具体发生的情况取决于您在向量中插入的位置,是否需要重新分配,以及元素是否具有noexcept移动构造函数。您所能依赖的是,不会有泄漏,也不会有部分构建的元素,因此向量处于有效状态。

当复制构造函数或复制赋值运算符引发异常时,这两种实现似乎都会使容器处于未定义状态(即,不清楚异常后容器包含哪些元素(

这不是一个未定义的状态,它只是一个你没有完整信息的状态。可以使用vector::size()vector::capacity()来确定其状态,并检查位置[0, size())处的图元以检查图元的状态。之后,你就知道了向量状态的一切。

即矢量始终处于有效状态,在检查之前,您只是不知道足够的信息来准确描述该状态。

"未定义"一词表示状态不一致或不可知,但事实并非如此。标准容器总是提供基本的异常安全保证,因此失败的操作不会使它们处于未定义的状态。

即使在vector::push_back()这样的极端情况下,元素类型不可复制并且具有抛出移动构造函数,异常也会使向量处于"未指定"状态,因此不会出现泄漏、无效对象和未定义行为。

例如,考虑第三种(混合(方法:

  • 创建一个模板对象val的n个副本的列表
  • 将该列表中的元素复制并分配到目标容器中

不同之处在于复制构造函数引发异常时对容器的影响。在这种情况下,如果复制赋值操作符抛出,则容器的内容保持不变。

也许我误解了,但我看不出这比"将构造元素一个接一个地直接复制到容器中各自的位置"有什么更好的。它执行的工作要多得多,做N个复制构造加上N个复制赋值,而不是N个复制构造,而且我认为在容器的最终状态方面没有任何优势。

在这两种情况下,您都需要向容器中添加n新元素,这可能会引发,如果它引发,我不明白为什么它会对最终状态产生任何影响,无论您是否也计划在之后进行一些额外的分配。