首页 > 解决方案 > 为什么 std::vector 允许对其包含的类型使用可抛出的移动构造函数?

问题描述

显然,std::move_if_noexcept()将调用移动构造函数,即使它没有被标记为noexcept好像没有可用的复制构造函数。

来自cpprefeerence.com(强调我的):

笔记

例如,这由 使用std::vector::resize,它可能必须分配新存储,然后将元素从旧存储移动或复制到新存储。如果在此操作期间发生异常,std::vector::resize则撤消它到目前为止所做的一切,这只有std::move_if_noexcept在用于决定是使用移动构造还是复制构造时才有可能。(除非复制构造函数不可用,在这种情况下,无论哪种方式都使用移动构造函数,并且可以放弃强异常保证)

std::vector在重新分配中使用此函数一样,这可能会使向量以及可能的应用程序处于未确定状态。那么,为什么会允许这样做呢?

标签: c++c++11

解决方案


假设你正在做vector它会使用时正在做的事情move_if_noexcept。也就是说,你有一些 object obj,你需要从 构造一个该类型的新值obj。之后,您将删除obj. 这是移动物体的主要情况vector,在可能的情况下也是如此。

如果运动是noexcept,那么obj根据定义,运动是异常安全的。如果不是 noexcept,那么您需要问:如果移动构造函数抛出,会发生什么?在那种情况下是什么状态obj?答案是……你不知道。更糟糕的是,您已经成功移动的任何对象的状态如何?你能把它们搬回来吗?

但是,当涉及到复制构造函数时,您确实知道. 复制构造函数将 aconst&传递给源对象。所以根据定义,一个失败的复制操作不能修改obj(是的,我们知道你可以const_cast,但这使你的复制构造函数成为一个谎言。这样的谎言是为什么auto_ptr不再存在,我猜想在谎言复制构造函数的标准)。因此,在失败的副本上,obj处于其原始状态。

因此,如果运动可以抛出,则首选复制,因为这提供了强大的异常保证:在发生异常时,一切都会恢复原状。

但是,如果您唯一的选择是投掷动作,那么您有两个选择:使这种类型永远不能与 一起使用vector,或者提供任何异常保证该类型本身在移动失败时提供的任何异常保证。后者是被选择的。

这是允许的,因为那是你要求的。您选择的类型不允许强异常保证,因此无法提供。但是你仍然可以制作一个vector这样的类型;您只需要处理不可复制的移动失败的可能性。

而且由于您是那种使用无法提供强大异常保证的类型的人,您显然必须知道如何处理这些情况,对吧?


推荐阅读