13

关于移动分配的标准库策略是允许实现假设自分配永远不会发生;在我看来,这似乎是一个非常糟糕的主意,因为:

  • C++ 中的“常规”(“副本”)分配合同一直被认为是安全的,可以防止自分配;现在我们要记住和解释另一个不连贯的 C++ 极端案例——也是一个非常危险的案例;我想我们都同意,C++ 需要的不是更多隐藏的陷阱;
  • 它使算法复杂化——remove_if家庭中的任何事情都需要处理这种极端情况;
  • 满足这个要求真的很容易——在你使用 swap 实现 move 的地方它是免费的,甚至在其他情况下(你可以通过 ad-hoc 逻辑获得一些性能提升)它只是一个单一的,(几乎)从未被采用分支,在任何 CPU 上几乎都是免费的¹;此外,在大多数有趣的情况下(涉及参数或局部变量的移动),优化器在内联时将完全删除分支(这几乎总是发生在“简单”移动赋值运算符中)。

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


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

4

2 回答 2

5

从对象中移出的对象std应该在被重用之前被丢弃或分配。 除此之外,任何不完全免费的东西都不会被承诺。

有时东西是免费的。就像 move-constructed-from 容器是空的一样。请注意,某些 move-assiged-from 案例没有这样的保证,因为某些实现可能会选择移动元素而不是缓冲区。为什么有区别?一个是免费的额外保证,另一个不是。

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

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

“做点小事,保证安全”的原则与“不使用的东西不买单”是冲突的。在这种情况下,第二个赢了。

于 2016-10-04T12:29:54.300 回答
5

Yakk 给出了一个非常好的答案(像往常一样并且被赞成),但这次我想添加更多信息。

在过去的五年中,关于自行分配的政策发生了一点变化。我们最近刚刚在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++11swap(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)仍然是(不那么昂贵的)无操作(使用移动而不是复制)。

细节:

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

为了完成这个无操作,self-move-assignment onx可以做任何它想做的事情,只要它x处于有效状态而不断言或抛出异常。

如果您想为您的类型指定Tself-move-assignment 是无操作的,那很好。std::lib 正是为unique_ptr.

如果您想为您的类型指定Uself-move-assignment 使其处于有效但未指定的状态,那也可以。std::lib 正是为vector. 一些实现(我相信VS)会在无操作的情况下进行自我移动分配vector。其他的则没有(例如 libc++)。

于 2016-10-05T03:17:23.607 回答