标准库中自我赋值 - 不安全移动赋值运算符的基本原理是什么?

时间:2016-10-04 12:12:59

标签: c++ c++11 move-semantics move-assignment-operator

关于移动分配的标准库政策是the implementation is allowed to assume that self-assignment will never happen;在我看来这是一个非常糟糕的主意,因为:

  • C ++中的“常规”(“复制”)转让合同一直被认为是安全的,不能自我分配;现在我们还有另一个C ++记忆和解释的不连贯的角落案例 - 以及一个微妙危险的案例;我想我们都同意C ++中需要的是而不是更隐藏的陷阱;
  • 它使算法变得复杂 - remove_if家庭中的任何事情都需要照顾这个角落;
  • 实现这个要求真的很容易 - 你用swap实现移动它是免费的,甚至在其他情况下(你可以通过ad-hoc逻辑获得一些性能提升)它只是一个,(几乎)从来没有采取分支,这在任何CPU¹几乎是免费的;此外,在大多数有趣的情况下(涉及参数或本地的移动),优化器在内联时将完全删除分支(对于“简单”移动赋值运算符几乎总是会发生这种情况)。

那么,为什么要做出这样的决定?

¹特别是在库代码中,实现者可以自由地利用关于“分支预期结果”的编译器特定提示(在VC ++中考虑gcc / __builtin_expect中的__assume)。

2 个答案:

答案 0 :(得分:5)

std中移出的对象应该被丢弃或在重用之前分配给它们。 任何未完全免费的都不会被承诺。

有时事情是免费的。就像一个移动构造的容器是空的。请注意,某些移动辅助的情况没有这样的保证,因为某些实现可能会选择移动元素而不是缓冲区。为什么不同?一个是免费的额外保证,另一个不是。

分支机构或其他支票并非完全免费。它占用了一个分支预测槽,即使预测它几乎是免费的。

最重要的是,a = std::move(a);是逻辑错误的证据。分配来自astd内)意味着您只会分配或放弃a。然而,在这里,你希望它在下一行有特定的状态。要么你知道你是自我分配,要么你不知道。如果你不这样做,你现在正在从你正在填充的对象移动,而你却不知道它。

“做一件小事以保证安全”的原则与“你不为你不使用的东西付钱”相冲突。在这种情况下,第二个赢了。

答案 1 :(得分:4)

Yakk gives a very good answer(像往常一样投票)但这次我想补充一点信息。

过去五年来,自动转让政策略有改变。我们最近在LWG 2468澄清了这个角落案例。实际上,我应该更准确一点:会议之间的非正式小组同意解决这个问题,并且可能会在下个月(2016年11月)投票进入C ++ 1z工作草案。

问题的要点是修改MoveAssignable要求,以澄清如果移动赋值的目标和来源是同一个对象,那么在赋值后对对象的值没有要求(除了它必须是有效的状态)。它进一步阐明了如果这个对象与std :: lib一起使用,它仍然必须满足算法的要求(例如LessThanComparable,无论它是移动分配的还是甚至是自我分配的。

因此...

T x, y;
x = std::move(y);  // The value of y is unspecified and x == the old y
x = std::move(x);  // The value of x is unspecified

xy仍处于有效状态。没有记忆被泄露。没有发生未定义的行为。

此职位的基本原理

它仍然是表现。然而,人们认识到swap(x, x)自C ++ 98以来一直是合法的,并且确实在野外发生。此外,由于C ++ 11 swap(x, x)x上执行自动移动分配:

T temp = std::move(x);
x = std::move(x);
x = std::move(temp);

在C ++ 11之前,swap(x, x)是(相当昂贵的)无操作(使用副本而不是移动)。 LWG 2468澄清了使用C ++ 11及之后,swap(x, x)仍然是一个(不太昂贵)的无操作(使用move而不是copy)。

详细说明:

T temp = std::move(x);
// temp now has the value of the original x, and x's value is unspecified
x = std::move(x);
// x's value is still unspecified
x = std::move(temp);
// x's value now has the value of temp, which is also the original x value

要完成此操作,x上的无操作,自动移动分配可以执行任何需要的操作,只要它使x处于有效状态而不断言或抛出异常。

如果您想为您的类型T指定自动分配是无操作,那就完全没问题了。 std :: lib完全与unique_ptr完全相同。

如果要为类型U指定自动移动分配使其处于有效但未指定的状态,那也没关系。 std :: lib完全与vector完全相同。一些实现(我相信VS)在vector无操作上进行自动移动分配。其他的不(如libc ++)。