标准库中自赋值不安全移动赋值运算符的基本原理是什么?
What is the rationale for self-assignment-unsafe move assignment operators in the standard library?
关于移动分配的标准图书馆政策是the implementation is allowed to assume that self-assignment will never happen;在我看来,这似乎是一个非常糟糕的主意,因为:
- C++ 中的 "regular" ("copy") 赋值契约一直被认为是安全的,可以防止自赋值;现在我们还有另一个 C++ 的不连贯的极端情况需要记住和解释——也是一个微妙的危险情况;我想我们都同意 C++ 需要的是 而不是 更多隐藏的陷阱;
- 它使算法复杂化 -
remove_if
系列中的任何东西都需要处理这种极端情况;
- 满足这个要求真的很容易——如果你用交换实现移动,它是免费的,甚至在其他情况下(你可以通过临时逻辑获得一些性能提升)它只是一个, (几乎)从未使用过分支,这在任何 CPU¹ 上几乎都是免费的;此外,在大多数有趣的情况下(涉及参数或局部变量的移动),分支将在内联时被优化器完全删除(对于 "simple" 移动赋值运算符,这几乎总是会发生)。
那么,为什么要做出这样的决定?
¹ 特别是在库代码中,实现者可以自由地利用有关 "branch expected outcome" 的编译器特定提示(想想 gcc 中的 __builtin_expect
/ VC++ 中的 __assume
) .
从 std
中的对象移动应该在重用之前被丢弃或分配给。 任何除此之外不完全免费的内容均不予承诺。
有时候东西是免费的。就像一个移动构造的容器是空的。请注意,某些 move-assiged-from 案例没有这样的保证,因为某些实现可能会选择移动元素而不是缓冲区。为什么不同?一个是免费的额外保证,另一个不是。
支票或其他支票并非完全免费。占用了一个分支预测槽位,即使预测也只是差不多空闲。
最重要的是,a = std::move(a);
是逻辑错误的证据。 Assign-from a
(within std
)意味着你只会赋值给或丢弃a
。然而在这里你希望它在下一行有特定的状态。要么你知道你在自我分配,要么你不知道。如果你不这样做,你现在正在从你也在填充的对象移动,而你不知道它。
"do little things to keep things safe"的原理与"you do not pay for that which you do not use"的原理冲突。在这种情况下,第二个赢了。
Yakk gives a very good answer(像往常一样,投票赞成)但这次我想补充一点信息。
self-move-assignment 的政策与过去 half-decade 相比发生了一点点变化。我们最近刚刚在 LWG 2468 中澄清了这个极端情况。实际上,我应该更准确地说:会议之间的一个非正式小组同意解决这个问题,并且很可能在下个月(2016 年 11 月)投票进入 C++1z 工作草案。
问题的要点是修改MoveAssignable
要求以明确如果移动分配的目标和来源相同object,则对值的要求没有要求赋值后的 object (除非它必须是有效状态)。进一步说明如果这个object是和std::lib一起使用的,它必须仍然满足算法的要求(例如LessThanComparable
)是否[=59] =] 是 move-assigned 甚至是自我 move-assigned.
所以...
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
但 x
和 y
仍处于有效状态。没有内存泄漏。没有发生未定义的行为。
这个职位的理由
还是表演。然而,人们认识到 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)
是(一个相当昂贵的)no-op(使用复制而不是移动)。 LWG 2468 阐明在 C++11 及之后的版本中,swap(x, x)
仍然是(不是那么昂贵)no-op(使用移动而不是复制)。
详情:
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
要完成此 no-op,x
上的 self-move-assignment 可以做任何它想做的事情,只要它使 x
处于有效状态而不断言或抛出异常.
如果你想为你的类型指定 T
self-move-assignment 是 no-op,那完全没问题。 std::lib 完全符合 unique_ptr
.
如果您想为您的类型指定 U
self-move-assignment 使其处于有效但未指定的状态,那也可以。 std::lib 与 vector
完全相同。一些实现(我相信 VS)会在 vector
上制造 self-move-assignment 一个 no-op。其他的不要(比如libc++)。
关于移动分配的标准图书馆政策是the implementation is allowed to assume that self-assignment will never happen;在我看来,这似乎是一个非常糟糕的主意,因为:
- C++ 中的 "regular" ("copy") 赋值契约一直被认为是安全的,可以防止自赋值;现在我们还有另一个 C++ 的不连贯的极端情况需要记住和解释——也是一个微妙的危险情况;我想我们都同意 C++ 需要的是 而不是 更多隐藏的陷阱;
- 它使算法复杂化 -
remove_if
系列中的任何东西都需要处理这种极端情况; - 满足这个要求真的很容易——如果你用交换实现移动,它是免费的,甚至在其他情况下(你可以通过临时逻辑获得一些性能提升)它只是一个, (几乎)从未使用过分支,这在任何 CPU¹ 上几乎都是免费的;此外,在大多数有趣的情况下(涉及参数或局部变量的移动),分支将在内联时被优化器完全删除(对于 "simple" 移动赋值运算符,这几乎总是会发生)。
那么,为什么要做出这样的决定?
¹ 特别是在库代码中,实现者可以自由地利用有关 "branch expected outcome" 的编译器特定提示(想想 gcc 中的 __builtin_expect
/ VC++ 中的 __assume
) .
从 std
中的对象移动应该在重用之前被丢弃或分配给。 任何除此之外不完全免费的内容均不予承诺。
有时候东西是免费的。就像一个移动构造的容器是空的。请注意,某些 move-assiged-from 案例没有这样的保证,因为某些实现可能会选择移动元素而不是缓冲区。为什么不同?一个是免费的额外保证,另一个不是。
支票或其他支票并非完全免费。占用了一个分支预测槽位,即使预测也只是差不多空闲。
最重要的是,a = std::move(a);
是逻辑错误的证据。 Assign-from a
(within std
)意味着你只会赋值给或丢弃a
。然而在这里你希望它在下一行有特定的状态。要么你知道你在自我分配,要么你不知道。如果你不这样做,你现在正在从你也在填充的对象移动,而你不知道它。
"do little things to keep things safe"的原理与"you do not pay for that which you do not use"的原理冲突。在这种情况下,第二个赢了。
Yakk gives a very good answer(像往常一样,投票赞成)但这次我想补充一点信息。
self-move-assignment 的政策与过去 half-decade 相比发生了一点点变化。我们最近刚刚在 LWG 2468 中澄清了这个极端情况。实际上,我应该更准确地说:会议之间的一个非正式小组同意解决这个问题,并且很可能在下个月(2016 年 11 月)投票进入 C++1z 工作草案。
问题的要点是修改MoveAssignable
要求以明确如果移动分配的目标和来源相同object,则对值的要求没有要求赋值后的 object (除非它必须是有效状态)。进一步说明如果这个object是和std::lib一起使用的,它必须仍然满足算法的要求(例如LessThanComparable
)是否[=59] =] 是 move-assigned 甚至是自我 move-assigned.
所以...
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
但 x
和 y
仍处于有效状态。没有内存泄漏。没有发生未定义的行为。
这个职位的理由
还是表演。然而,人们认识到 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)
是(一个相当昂贵的)no-op(使用复制而不是移动)。 LWG 2468 阐明在 C++11 及之后的版本中,swap(x, x)
仍然是(不是那么昂贵)no-op(使用移动而不是复制)。
详情:
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
要完成此 no-op,x
上的 self-move-assignment 可以做任何它想做的事情,只要它使 x
处于有效状态而不断言或抛出异常.
如果你想为你的类型指定 T
self-move-assignment 是 no-op,那完全没问题。 std::lib 完全符合 unique_ptr
.
如果您想为您的类型指定 U
self-move-assignment 使其处于有效但未指定的状态,那也可以。 std::lib 与 vector
完全相同。一些实现(我相信 VS)会在 vector
上制造 self-move-assignment 一个 no-op。其他的不要(比如libc++)。