fill-insert()-复制构造函数和复制赋值noexcept状态
fill insert() - copy constructor and copy assignment noexcept status?
- STL容器元素是否需要具有
noexcept
复制构造函数和复制赋值运算符?如果可能的话,请提供参考资料 - 如果不是,在多次插入期间发生异常时,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但是,只有当相关操作(在本例中为复制构造函数(本身没有抛出异常时,才适用该保证,这正是我的问题。
据我所知,这留下了两种基本的实现方法:
- 为新元素创建伪条目,并复制分配
val
。只有当可以通过复制容器中的现有元素来创建伪元素时,或者当value_type
是DefaultConstructible时(这不是必需的(,这才有效 - 将构造元素一个接一个地直接复制到容器中它们各自的位置。这似乎或多或少是规范化的实现
编辑2:我不会把这个称为未定义行为,因为这个词似乎让人们认为未定义为语言运行时/标准。
当复制构造函数或复制赋值运算符引发异常时,这两个实现似乎都会使容器留下未知内容(即,不清楚容器在异常后包含哪些元素(。
编辑1:请注意,这并不意味着我认为C++运行时存在不良行为,例如内存泄漏或未定义的值。然而,似乎或多或少有未指定容器的内容。特别是,容器中的内容可能已经完全(尽管一直(改变了。
例如,考虑第三种(混合(方法:
- 创建模板对象CCD_ 7的CCD_
- 将该列表中的元素复制并分配到目标容器中
不同之处在于复制构造函数引发异常时对容器的影响。在这种情况下,如果复制构造函数抛出,容器的内容将保持不变(但当复制赋值运算符抛出时,仍会导致未指定的内容(。当使用指针时(即,当不使用std::vector
时(,可能会忽略复制分配,只重新排列指针,从而使操作成为原子写入异常。
至于容器元素上的noexcept
:对象是通过allocator_traits<value_type>::construct(ptr, args)
创建的,而不是noexcept
,我也找不到容器元素大多数都有noexcept
复制构造函数/复制赋值运算符的要求(例如std::shared_ptr
和std::unique_ptr
需要这样(。
请注意,为复制构造和复制分配自动生成的操作要求为noexcept
,除非它们被要求调用本身可能引发异常的操作。
这让我很困惑,我确信我错过了一些东西,但我无法找出标准中可能证明我错了的部分。
- STL容器元素是否需要有noexcept复制构造函数和复制赋值运算符?如果可能的话,请提供参考资料
不,它们不是,我不能为不存在的需求提供参考,因为它不存在。
如果需要,它会在标准中这样说,但它没有。
一般情况下,元素甚至根本不需要有一个复制构造函数,对于大多数操作来说,一个移动构造函数就足够了。副本分配也是如此。
- 如果不是,当在多重插入过程中发生异常时,STL容器的状态是什么,例如在填充插入过程中
它取决于容器,以及您要插入的容器中的位置。
对于基于节点的容器(如列表(,如果一次插入抛出,则已插入的任何元素都将保留在容器中。
对于std::vector
,具体发生的情况取决于您在向量中插入的位置,是否需要重新分配,以及元素是否具有noexcept移动构造函数。您所能依赖的是,不会有泄漏,也不会有部分构建的元素,因此向量处于有效状态。
当复制构造函数或复制赋值运算符引发异常时,这两种实现似乎都会使容器处于未定义状态(即,不清楚异常后容器包含哪些元素(
这不是一个未定义的状态,它只是一个你没有完整信息的状态。可以使用vector::size()
和vector::capacity()
来确定其状态,并检查位置[0, size())
处的图元以检查图元的状态。之后,你就知道了向量状态的一切。
即矢量始终处于有效状态,在检查之前,您只是不知道足够的信息来准确描述该状态。
"未定义"一词表示状态不一致或不可知,但事实并非如此。标准容器总是提供基本的异常安全保证,因此失败的操作不会使它们处于未定义的状态。
即使在vector::push_back()
这样的极端情况下,元素类型不可复制并且具有抛出移动构造函数,异常也会使向量处于"未指定"状态,因此不会出现泄漏、无效对象和未定义行为。
例如,考虑第三种(混合(方法:
- 创建一个模板对象val的n个副本的列表
- 将该列表中的元素复制并分配到目标容器中
不同之处在于复制构造函数引发异常时对容器的影响。在这种情况下,如果复制赋值操作符抛出,则容器的内容保持不变。
也许我误解了,但我看不出这比"将构造元素一个接一个地直接复制到容器中各自的位置"有什么更好的。它执行的工作要多得多,做N个复制构造加上N个复制赋值,而不是N个复制构造,而且我认为在容器的最终状态方面没有任何优势。
在这两种情况下,您都需要向容器中添加n
新元素,这可能会引发,如果它引发,我不明白为什么它会对最终状态产生任何影响,无论您是否也计划在之后进行一些额外的分配。
- CRTP 中的复制赋值运算符 - gcc vs clang 和 msvc
- 如何在双向链表上实现复制赋值?
- 复制赋值函数如何访问另一个对象的私有成员(Stroustroup 原则和实践书)?
- 为什么基类中的复制和交换会导致派生类中的复制赋值运算符被隐式删除?
- 在c++中重载复制赋值运算符
- 复制构造函数和复制赋值运算符是否应具有相同的语句?
- 移动赋值运算符与复制赋值运算符
- Gcc 使用 memcpy 作为隐式复制赋值运算符,而不是成员复制
- 为什么 GCC 拒绝复制赋值操作中的常量引用
- 复制赋值构造函数中的aligned_alloc内存块在释放时崩溃
- 复制赋值和复制构造函数(代码C++的差异)
- 如何在没有复制赋值运算符的情况下交换两个对象
- 对于具有抛出复制构造函数和noexcept-by-value复制赋值的类,is_nothrow_copy_assigna
- 模拟 C++ 中 lambda 的复制赋值运算符
- 重载复制赋值运算符
- 为什么C++编译器会创建复制构造函数和复制赋值运算符
- 为什么在进行复制赋值之前调用复制构造函数
- 为什么当复制赋值函数不返回任何内容时编译器不引发错误?
- 默认的复制构造函数和复制赋值运算符给出奇怪的错误
- 重载运算符 = 返回 void 是否不可能成为复制赋值运算符