显然,std::move_if_noexcept()
将调用move构造函数,即使没有副本构造函数可用,即使未标记为noexcept
也是如此。
来自cpprefeerence.com(重点是我):
注释
例如
std::vector::resize
使用它,它可能必须分配新的存储,然后将元素从旧的存储移动或复制到新的存储。如果在此操作期间发生异常,std::vector::resize
会撤消到目前为止所做的一切,只有在使用std::move_if_noexcept
来决定使用移动构造还是复制构造的情况下,这才是可能的。 (除非复制构造函数不可用,否则将以两种方式使用move构造函数,并且可以放弃强异常保证)
由于std::vector
在重新分配时使用此函数,可能使向量以及应用程序处于不确定状态。那么,为什么允许这样做?
答案 0 :(得分:2)
比方说,您正在做vector
在使用move_if_noexcept
时所做的事情。也就是说,您有一些对象obj
,并且需要从obj
构造该类型的新值。然后,您将删除obj
。这是移动对象的主要情况,因此vector
会尽可能这样做。
如果移动为noexcept
,那么按照定义,移动obj
是异常安全的。如果不是 noexcept
,那么您需要问:如果move构造函数抛出异常会怎样?在这种情况下,obj
的状态是什么?答案是……你不知道。更糟糕的是,已经成功移动的任何对象的状态如何?您可以将它们后退吗?
但是,在复制构造函数时,您不知道。复制构造函数将const&
带到源对象。因此,根据定义,失败的复制操作 不能修改obj
(是的,我们知道您可以const_cast
,但是这会使您的复制构造函数成为 lie 。像这样的谎言就是auto_ptr
不再存在的原因,我想标准中对说谎的副本构造函数有全面的禁止)。因此,在失败的副本上,obj
处于原始状态。
因此,如果可以抛出移动,则首选复制,因为这提供了强有力的异常保证:在发生异常的情况下,一切都会恢复原状。
但是,如果您唯一的选择是掷球,那么您有两种选择:选择使其不能与vector
一起使用,或者提供任何异常保证类型本身会导致运动失败。而选择的是后者。
之所以允许,是因为这就是您要的。您选择的类型不允许强异常保证,因此无法提供。但是您仍然可以制作此类的vector
;您只需要解决不可复制的移动失败的可能性。
既然您是那种使用不能提供强有力的异常保证的类型的人,那么您显然必须知道如何处理这些情况,对吗?