使用std :: string的移动赋值运算符(在VC11中)需要什么?
我希望它会被自动使用,因为分配后不需要v。 在这种情况下是否需要std :: move?如果是这样,我不妨使用非C ++ 11交换。
#include <string>
struct user_t
{
void set_name(std::string v)
{
name_ = v;
// swap(name_, v);
// name_ = std::move(v);
}
std::string name_;
};
int main()
{
user_t u;
u.set_name("Olaf");
return 0;
}
答案 0 :(得分:11)
我希望它会被自动使用,因为分配后不需要v。在这种情况下是否需要std :: move?
必须为左值明确说明移动,除非它们是从函数返回(按值)。
这可以防止意外移动。记住:运动是一种破坏性行为;你不希望它发生。
另外,如果name_ = v;
的语义根据这是否是函数中的最后一行而改变,那将会很奇怪。毕竟,这是完全合法的代码:
name_ = v;
v[0] = 5; //Assuming v has at least one character.
为什么第一行有时会执行一次复制而另一次会移动?
如果是这样,我不妨使用非C ++ 11交换。
你可以随意做,但std::move
对于意图更明显。我们知道它意味着什么以及你正在做什么。
答案 1 :(得分:7)
接受的答案是一个很好的答案(我已经对它进行了投票)。但我想更详细地解决这个问题:
我的问题的核心是:为什么不选择移动任务 操作员自动?编译器知道后面没有使用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
复制构造后是否真的不再使用x2
。
因此,最初的“移动”提案给出了一个非常简单且非常保守的隐含动作的规则:
只有在复制的情况下才能隐式移动左值 elision已被允许。
例如:
#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
转移。风险看起来很小,利益看起来很强大。这不仅意味着许多现有程序通过简单的重新编译会变得更快,但这也意味着我们现在可以从工厂函数返回“仅移动”类型。这对风险比率有很大的好处。
在标准化过程的后期,我们有点贪心,并且还说当返回一个by-value参数(并且类型与返回类型匹配)时会发生隐式移动。这里的好处似乎也相对较大,但代码破损的可能性略大,因为这不是RVO合法的情况。但是我没有为这种情况打破代码的演示。
所以最终,你的核心问题的答案是,移动语义的原始设计在破坏现有代码方面采取了非常保守的路线。如果不是,它肯定会被委员会击落。在这个过程的后期,有一些变化使设计更具侵略性。但到目前为止,核心提案已经在大多数(但并非一致)的支持下牢牢确立了标准。
答案 2 :(得分:5)
在您的示例中,set_name
按值获取字符串。但是,set_name
内v
是一个左值。让我们分开处理这些案例:
user_t u;
std::string str("Olaf"); // Creates string by copying a char const*.
u.set_name(std::move(str)); // Moves string.
在set_name
内调用std::string
的赋值运算符,
这会产生不必要的副本。但是operator=
还有一个rvalue
overload,
在你的情况下更有意义:
void set_name(std::string v)
{
name_ = std::move(v);
}
这样,发生的唯一复制就是字符串构造
(std::string("Olaf")
)。