为什么 std::vector 允许对其包含的类型使用可抛出的移动构造函数?

Why does std::vector allow the use of a throwable move constructor for the type it contains?

本文关键字:移动 构造函数 类型 vector 为什么 包含 std      更新时间:2023-10-16

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

从 cpprefeerence.com(强调我的):

笔记

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

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

假设您正在使用move_if_noexcept时做vector正在做的事情。也就是说,您有一些对象obj,并且您需要从obj构造该类型的新值。之后,您将删除obj.这是移动对象的主要情况,因此vector尽可能这样做。

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

但是,当涉及到复制构造函数时,您确实知道。复制构造函数将const&带到源对象。因此,根据定义,失败的复制操作无法修改obj(是的,我们知道您可以const_cast,但这会使您的复制构造函数成为谎言。像这样的谎言是auto_ptr不再存在的原因,我猜在标准中全面禁止撒谎的副本构造函数)。因此,在失败的副本上,obj处于其原始状态。

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

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

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

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