【问题标题】:Could it be possible to have types with move operations that throw in containers?是否有可能拥有带有移动操作的类型,这些类型会放入容器中?
【发布时间】:2018-11-08 10:37:24
【问题描述】:

在和同事解释对象的移动操作时,我基本上说移动操作不应该在容器中抛出异常,因为如果移动操作失败,那么就没有办法可靠地恢复原始对象。再想一想,我想知道这是否不正确,如果移动操作确实抛出,它可以将原始对象恢复到原始状态。

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

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

这只能在默认的移动运算符上实现,如果有任何额外的逻辑,编译器可能很难进行反向部分转换。

我是否过于简单化了?是否有什么我错过的东西可以让容器在没有非抛出移动构造函数/操作符的情况下移动对象?

【问题讨论】:

  • 当 move ctor 可以抛出时,vector 不会退回到副本上吗?
  • @JVApen,是的。我想知道是否可以使用会抛出的移动构造函数。
  • 注意:不仅移动构造函数必须不抛出标准容器移动,而且必须声明noexcept
  • vector 的问题是还原 已完成 动作。你需要移动六,移动三,第四会抛出异常。即使您要求不更改第四个,您仍然没有可靠的方法来恢复前三个。
  • 是的@ArneVogel,我提到了here

标签: c++ c++11 stl c++14 stdmove


【解决方案1】:

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

假设您有一个vector,包含 10 个投掷移动元素。 vector 需要自行调整大小。因此它将 5 个对象移动到新内存中,但第 6 个对象会抛出。好吧,没关系;构造失败,因此假设第 6 个对象的值很好。也就是说,无论该类型的异常保证是什么,事情都会如何工作。

但是,由于一个对象的移动失败,vector 需要将最后 5 个对象back 移动到第一个数组,因为vector 试图提供强大的异常保证。这是个问题,因为向后移动本身可能会失败

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

vector 也是如此。如果退回失败,vector 没有理智的答案。因此,如果vector 不能保证恢复其先前的数组状态是noexcept,那么它将使用复制,因为这可以提供这种保证。

【讨论】:

  • 可以通过使用旋转操作和删除来解决这个问题,对吧?只是会贵一点。
  • @Adrian:旋转并删除什么?如果该类型具有投掷动作,那么这个“旋转”操作将如何以noexcept 方式工作?
  • 旋转操作随时在两个元素之间交换。因此,所有元素仍然可以从中恢复。它类似于过滤向量中的元素。项目在向量内交换并在其末尾累积,此时它们被删除。唯一缺少的部分是一些编译器魔术来恢复失败的移动和所有之前的移动。当然,这变得更加复杂,因此需要更多时间。我只是在大声思考。
  • 请注意,并非所有vector 操作都有强有力的保证。其中一些,例如eraseinsert(最后除外)会移动元素,如果移动抛出,则不会强制恢复。因此在这些操作中可以使用投掷动作。
  • std::rotate 不强制使用swap,它可以移出到临时位置。如果从临时抛出返回,您将获得一个可以消失的值。此外,如果使用一般的std::swapswap 本身可能会将值搁置在局部变量中。所以使用std::rotate 并不能解决这个问题。
【解决方案2】:

首先,我很难想象在移动操作中获取资源的对象。想一想——unique_ptr 只是传递指针而不获取任何东西,shared_ptr 也是如此。 stringvector,所有容器等只是窃取指向先前在默认或复制构造函数中获取的资源的指针。我觉得从移动构造函数中抛出就像从析构函数中抛出一样。当然,继续,在你的膝盖上开枪。但是好的,我可以接受存在例外。

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

【讨论】:

  • 这个问题只是猜测。我试图确定是否为没有非抛出移动构造函数/运算符的类型定义容器。
  • std::list 和其他基于节点的容器不需要 noexcept 移动构造函数或默认构造函数。这允许实现始终有一个空节点,当您从 std::list 移动时会发生这种情况。
  • 是的,@NicolBolas,你的权利。它并不适用于所有容器,只是向量和类似的容器,它们的分配方式使得插入/删除时需要移动/复制。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-06
  • 1970-01-01
  • 2017-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多