接受的答案是一个很好的答案(我已经赞成)。但我想更详细地解决这个问题:
我的问题的核心是:为什么它不自动选择移动赋值运算符?编译器知道 v 在赋值后没有使用,不是吗?还是 C++11 不需要编译器那么聪明?
在设计移动语义时考虑了这种可能性。在极端情况下,您可能希望编译器进行一些静态分析并尽可能从对象中移动:
void set_name(std::string v)
{
name_ = v; // move from v if it can be proven that some_event is false?
if (some_event)
f(v);
}
最终要求编译器进行这种分析是非常棘手的。一些编译器可能能够证明,而其他编译器可能不能。从而导致代码不是真正可移植的。
好的,那么一些没有 if 语句的更简单的情况呢?
void foo()
{
X x;
Y y(x);
X x2 = x; // last use? move?
}
好吧,很难知道是否y.~Y()
会通知x
已被移出。一般来说:
void foo()
{
X x;
// ...
// lots of code here
// ...
X x2 = x; // last use? move?
}
编译器很难对此进行分析以知道x
在复制构造后是否真的不再使用 to x2
。
所以最初的“移动”提议给出了一个非常简单且非常保守的隐式移动规则:
只有在已经允许复制省略的情况下,左值才能被隐式移动。
例如:
#include <cassert>
struct X
{
int i_;
X() : i_(1) {}
~X() {i_ = 0;}
};
struct Y
{
X* x_;
Y() : x_(0) {}
~Y() {assert(x_ != 0); assert(x_->i_ != 0);}
};
X foo(bool some_test)
{
Y y;
X x;
if (some_test)
{
X x2;
return x2;
}
y.x_ = &x;
return x;
}
int main()
{
X x = foo(false);
}
在这里,根据 C++98/03 规则,这个程序可能会或可能不会断言,这取决于是否return x
发生复制省略。如果确实发生了,程序运行良好。如果没有发生,则程序断言。
因此有理由认为:当允许 RVO 时,我们已经处于无法保证x
. 所以我们应该能够利用这个余地并从x
. 风险看起来很小,收益看起来很大。这不仅意味着通过简单的重新编译,许多现有程序会变得更快,而且还意味着我们现在可以从工厂函数返回“仅移动”类型。这是一个非常大的收益风险比。
在标准化过程的后期,我们有点贪心,还说在返回按值参数时会发生隐式移动(并且类型与返回类型匹配)。这里的好处似乎也相对较大,尽管代码破坏的机会稍大一些,因为这不是 RVO 合法(或现在)合法的情况。但是我没有演示这种情况下的破解代码。
所以最终,你的核心问题的答案是移动语义的原始设计在破坏现有代码方面采取了非常保守的路线。如果不是这样,它肯定会在委员会中被击落。在这个过程的后期,有一些变化使设计更具侵略性。但到了这个时候,核心提案已经在标准中根深蒂固,得到了多数(但不是一致)的支持。