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

dhk*_*hke 3 c++ stl insert copy-constructor copy-assignment

  1. STL容器元素是否需要具有noexcept复制构造函数和复制赋值运算符?如果可能,请提供参考.
  2. 如果不是,当多插入期间发生异常时,例如在填充插入期间,STL容器的状态是什么.

尝试编写允许拦截/否决修改的通用包装时会出现问题.我能提出的任何实现都可能会改变底层容器的语义,除非专门针对每个容器类型(这不是一个真正的选项).

例如,std::vector有一个填充插入:

void insert (iterator position, size_type n, const value_type& val);
Run Code Online (Sandbox Code Playgroud)

这需要value_type同时是CopyInsertableCopyAssignable.需要注意的是它并没有要求值类型为缺省构造.

编辑3 Stroustrup本人(第956页的表)表明多元素插入应该对所有向量,双端队列,列表和映射具有强大的保证.这意味着完整的标准库操作要么原子成功要么失败.

编辑4但是,保证仅适用于相关操作(在本例中为复制构造函数)本身不会抛出异常,这正是我的问题.

据我了解,这留下了两个基本的实现方法:

  1. 为新元素和复制分配创建虚拟条目val.这只有在可以通过复制容器中的现有元素或者何时value_typeDefaultConstructible(这不是必需的)创建虚拟元素时才有效.
  2. 复制构造元素一个接一个地直接进入容器中它们各自的位置.这似乎或多或少是规范的实现.

编辑2:我不会调用这种未定义的行为,因为该术语似乎会使人们认为未定义为语言运行时/标准.

当复制构造函数或复制赋值运算符引发异常时,这两种实现似乎都会使容器具有未知内容(即,在异常之后不清楚容器占用哪些元素).

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

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

  1. 创建n模板对象的副本列表val.
  2. 将此列表中的元素复制分配到目标容器中.

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

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

请注意,noexcept除非需要调用本身可能引发异常的操作,否则需要自动生成copy-construct和copy-assign 操作.

这让我感到困惑,我确信我错过了一些东西,但我无法弄清楚可能证明我错了的标准部分.

Jon*_*ely 5

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

不,他们不是,我不能提供不存在的要求的参考,因为它不存在.

如果需要它会在标准中这样说,但事实并非如此.

通常,元素根本不需要复制构造函数,移动构造函数对于大多数操作来说已经足够了.同样适用于副本分配.

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

这取决于容器,以及容器插入的位置.

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

对于std::vector究竟会发生什么取决于Vector中要插入以及是否需要重新分配,以及是否元素有noexcept移动构造函数或没有.您可以依赖的是没有泄漏且没有部分构造的元素,因此向量处于有效状态.

当复制构造函数或复制赋值运算符引发异常时,这两个实现似乎都将容器置于未定义状态(即,在异常之后不清楚容器保持哪些元素).

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

即,矢量始终处于有效状态,您只是不知道足够的信息来准确描述该状态,直到您检查它为止.

"未定义"这个词表明国家是不一致的,或者是不可知的,这是不正确的.标准容器始终提供基本的异常安全保证,因此失败的操作不会使它们处于未定义状态.

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

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

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

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

也许我误解了,但我不知道这有什么比"一个接一个地将元素直接复制到容器中各自的位置"更好.它执行了相当多的工作,执行N个复制构造和N个复制分配,而不是N个复制构造,并且我认为在容器的最终状态方面没有任何优势.

在这两种情况下,你需要向n容器添加新元素,这可能会抛出,如果它抛出,我不明白为什么它对最终状态有任何影响,你是否还计划在之后做一些额外的任务.