问题 是否可以使用带有容器的移动操作的类型?


在解释与同事对对象的移动操作时,我基本上说移动操作不应该在容器中抛出异常,因为如果移动操作失败,则无法可靠地恢复原始对象。考虑到这一点,我想知道这是不是正确的,并且如果一个移动操作确实抛出,它可以将原始对象恢复到它的原始状态。

这样做的原因是,如果一个对象可以抛出,那么它会因为复制或将包含的对象从旧地址移动到新地址而抛出,但如果资源未能获取则抛出。所以所有的原始信息都应该存在。如果是这种情况,那么编译器是否应该无法反转它为重建原始对象所做的操作?

操作可能是一种方式,比如移动一个整数,但在这种情况下它可能只是终止应用程序,也许如果开发人员想避免单向操作可以使用交换方法。

这只能在默认移动运算符上实现,就像有任何其他逻辑一样,编译器可能很难进行反向部分变换。

我是否过于简化了事情?有没有我遗漏的东西,如果没有非投掷移动构造函数/操作符,容器不会移动对象?


11472
2018-05-29 17:40


起源

当移动ctor可以扔掉时,矢量是否会落在副本上? - JVApen
@JVApen,是的。我想知道是否可以使用一个抛出的移动构造函数。 - Adrian
注意:移动构造函数不仅不必抛出std容器来移动,而且必须声明它 noexcept - Justin
vector问题在于恢复 完成 移动。你需要移动六个,移动三个,第四个抛出异常。即使你要求第四个没有改变,你仍然没有可靠的方法来恢复前三个。 - T.C.
是的,@ ArneVogel,我提到过 这里。 - Adrian


答案:


您可以在容器中使用投掷移动的类型 vector 这可以移动他们的元素。但是,这样的容器 将不会 使用投掷移动操作。

假设你有一个 vector 10投掷移动元素。而且 vector 需要调整自己的大小。所以它将5个对象移动到新的内存中,但是第6个抛出。嗯,那没关系;构造失败,所以假设第6个对象的值很好。也就是说,无论那种类型的异常保证是什么,它将是如何工作的。

但是,因为一个物体的运动失败了, vector 需要移动最后5个对象 背部 从那以后到第一个阵列 vector 正试图提供强有力的例外保证。这是一个问题,因为回来了 本身就会失败

当修复故障本身的过程失败时,C ++通常没有有效的答案。你可以在例外中看到;您不能从由于异常失败而在展开过程中调用的析构函数中发出异常。 std::terminate 在这种情况下发生。

同样的道理 vector。如果搬回失败, vector 没有理智的答案。因此,如果 vector 不能 保证 恢复其先前的数组状态是 noexcept,然后它将使用复制,因为这可以提供该保证。


10
2018-05-29 17:56



可以通过使用旋转操作来解决这个问题并删除,对吗?这会贵一点。 - Adrian
@Adrian:旋转并删除什么?如果类型具有抛掷移动,那么这种“旋转”操作将如何工作 noexcept 时尚? - Nicol Bolas
旋转操作在任何时间在两个元素之间交换。因此,所有元素仍然可以从中恢复。它类似于向量中的过滤元素。项目在向量内交换并在其末尾累积,此时它们将被删除。唯一缺少的是一些编译器魔法来恢复失败的移动和所有前面的移动。当然,这变得更加复杂,需要更多时间。我只是在大声思考。 - Adrian
值得注意的是,不是全部 vector 运营有很强的保障。其中一些,如 erase 和 insert (除了最后),将移动元素,如果移动,则不会强制恢复。因此,投掷动作可用于那些操作。 - Howard Hinnant
std::rotate 没有强制要求使用 swap,它可以移出一个临时的。如果从临时抛出回来,你有一个可以消失的值。另外,如果一般 std::swap 用来, swap 本身可以将值搁置在局部变量中。所以使用 std::rotate 没有解决这个问题。 - Howard Hinnant


首先,我很难想象在移动操作中获取资源的对象。想一想 - unique_ptr只是传递指针而不获取任何东西,相同的 shared_ptrstringvector,所有的包含等只是窃取指向早期默认或复制构造函数中获取的资源的指针。我觉得从移动构造函数抛出就像从析构函数中抛出一样。当然,继续拍你自己的膝盖。但好吧,我可以接受存在的例外情况。

所以让我们转到第二点 - 当移动时,实际上两个对象(移动和移动)都是无效的。从这种情况回滚将需要额外的“魔术”功能来调用其中一个。所以似乎数据无法修复,因为标准没有定义这样的功能。


0
2018-05-29 18:00



这个问题只是猜想。我试图确定是否为没有非投掷移动构造函数/运算符的类型定义容器。 - Adrian
std::list 和其他基于节点的容器不需要 noexcept 移动构造函数或默认构造函数。这允许实现总是有一个空节点,这是当你从a移动时发生的 std::list。 - Nicol Bolas
是的,@ NikolBolas,你的权利。它不是通用的所有容器,只是vector和类似的,它们以一种方式分配,当插入/删除时需要移动/复制。 - Adrian