提问人:dhke 提问时间:8/24/2015 最后编辑:dhke 更新时间:8/25/2015 访问量:340
fill insert() - 复制构造函数和复制赋值 noexcept status?
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);
这需要同时是 CopyInsertable 和 CopyAssignable。请注意,它不要求值类型为 DefaultConstructible。value_type
编辑 3 Stroustrup 本人(第 956 页的表格)指出,多元素插入应该对所有向量、deque、list 和 map 都有强有力的保证。这意味着完整的标准库操作在原子上成功或失败。
编辑 4但是,只有当相关操作(在本例中为复制构造函数)本身不会抛出异常时,该保证才适用,这正是我的问题。
据我了解,这留下了两种基本的实现方法:
- 为新元素创建虚拟条目并复制分配 .仅当可以通过复制容器中的现有元素或 DefaultConstructible(这不是必需的)来创建虚拟元素时,这才有效。
val
value_type
- 将元素一个接一个地复制构造到容器中的相应位置。这似乎或多或少是规范的实现。
编辑 2:我不会称其为未定义的行为,因为该术语似乎会提醒人们将未定义的行为视为语言运行时/标准。
当复制构造函数或复制赋值运算符引发异常时,这两种实现似乎都给容器留下了未知的内容(即不清楚容器在异常后包含哪些元素)。
编辑1:请注意,这并不意味着我认为C++运行时存在不良行为,例如内存泄漏或未定义的值。但是,似乎或多或少没有具体说明容器的内容是什么。特别是,容器的内容物可能已经完全(尽管始终如一)地被改变。
例如,考虑第三种(混合)方法:
- 创建模板对象的副本列表。
n
val
- 将此列表中的元素复制并分配到目标容器中。
区别在于复制构造函数引发异常时对容器的影响。在这种情况下,如果复制构造函数抛出容器的内容,则容器的内容保持不变(但在复制赋值运算符抛出时仍会导致未指定的内容)。当使用指针时(即不使用指针时),复制赋值可能会被省略,只有指针被重新排列,使操作原子 wrt。异常。std::vector
至于容器元素:对象是通过 创建的,但事实并非如此,我也找不到容器元素大多数具有复制构造函数/复制同化运算符的要求(例如 并要求这样做)。noexcept
allocator_traits<value_type>::construct(ptr, args)
noexcept
noexcept
std::shared_ptr
std::unique_ptr
请注意,自动生成的 copy-construct 和 copy-assign 操作是必需的,除非它们需要调用本身可能引发异常的操作。noexcept
这让我感到困惑,我确信我错过了一些东西,但我无法弄清楚标准中可能证明我错了的部分。
答:
- STL 容器元素是否需要具有 noexcept 复制构造函数和复制赋值运算符?如果可能,请提供参考资料。
不,它们不是,我无法为不存在的需求提供参考,因为它不存在。
如果需要,它会在标准中这样说,但事实并非如此。
一般来说,元素甚至根本不需要具有复制构造函数,移动构造函数对于大多数操作来说就足够了。复制分配也是如此。
- 如果不是,当在多次插入期间发生异常时,例如在填充插入期间,STL 容器的状态是什么。
这取决于容器,以及您在容器中插入的位置。
对于基于节点的容器(如列表),如果一个插入引发,则已插入的任何元素都将保留在容器中。
因为究竟会发生什么取决于您在向量中插入的位置、是否需要重新分配,以及元素是否具有 noexcept 移动构造函数。您唯一可以依赖的是不会有泄漏,也不会有部分构造的元素,因此矢量处于有效状态。std::vector
当复制构造函数或复制赋值运算符引发异常时,这两种实现似乎都使容器处于未定义状态(即不清楚容器在异常后包含哪些元素)。
这不是一个未定义的状态,它只是一个你没有完整信息的状态。您可以使用 和 来确定其状态,并检查位置的元素以检查元素的状态。之后,您就知道了有关向量状态的所有信息。vector::size()
vector::capacity()
[0, size())
也就是说,向量始终处于有效状态,在检查它之前,您只是不知道足够的信息来准确描述该状态。
“未定义”一词表示状态不一致或不可知,这是不正确的。标准容器始终提供基本的异常安全保证,因此失败的操作不会使它们处于未定义的状态。
即使在极端情况下,例如元素类型不可复制且具有抛出移动构造函数,异常也会使向量处于“未指定”状态,因此没有泄漏,没有无效对象,也没有未定义的行为。vector::push_back()
例如,考虑第三种(混合)方法:
- 创建包含模板对象 VAL 的 N 个副本的列表。
- 将此列表中的元素复制并分配到目标容器中。
区别在于复制构造函数引发异常时对容器的影响。在这种情况下,如果 copy-assignment 运算符抛出,则容器的内容保持不变。
也许我误解了,但我看不出这比“将元素一个接一个地直接复制到容器中的各自位置”更好。它执行了相当多的工作,执行 N 个副本构造和 N 个副本分配,而不是 N 个副本构造,而且就容器的最终状态而言,我看不出有任何优势。
In both cases you need to add new elements to the container, which could throw, and if it throws I don't see why it makes any difference to the final state whether you were also planning to do some extra assignments afterwards.n
评论
insert
仅提供基本的异常保证(“无泄漏”)。如果其中一个复制构造函数抛出,则向量不会处于未定义状态。它将包含 给出的对象数。size
vector
deque
list