搬家:需要什么?

XTF*_*XTF 6 c++ visual-c++ move-semantics c++11 visual-studio-2012

使用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;
}
Run Code Online (Sandbox Code Playgroud)

Nic*_*las 11

我希望它会被自动使用,因为在任务完成后不再需要v.在这种情况下是否需要std :: move?

必须为左值明确说明移动,除非它们是从函数返回(按值).

这可以防止意外移动的东西.记住:运动是一种破坏性行为; 你不希望它发生.

另外,如果name_ = v;基于这是否是函数中的最后一行而改变了语义,那将是奇怪的.毕竟,这是完全合法的代码:

name_ = v;
v[0] = 5; //Assuming v has at least one character.
Run Code Online (Sandbox Code Playgroud)

为什么第一行有时会执行一次复制而另一次会移动?

如果是这样,我不妨使用非C++ 11交换.

你可以随意做,但std::move意图更明显.我们知道这意味着什么以及您正在做什么.


How*_*ant 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);
}
Run Code Online (Sandbox Code Playgroud)

最终要求编译器进行这种分析非常棘手.有些编译器可能能够提供证据,而其他编译器则可能没有.因此导致代码不是真正可移植的.

好的,那么没有if语句的一些更简单的情况呢?

void foo()
{
    X x;
    Y y(x);
    X x2 = x;  // last use?  move?
}
Run Code Online (Sandbox Code Playgroud)

好吧,很难知道是否y.~Y()会发出通知x.总的来说:

void foo()
{
    X x;
    // ...
    // lots of code here
    // ...
    X x2 = x;  // last use?  move?
}
Run Code Online (Sandbox Code Playgroud)

编译器很难分析它以了解x在复制构造之后是否真的不再使用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);
}
Run Code Online (Sandbox Code Playgroud)

这里,根据C++ 98/03规则,该程序可能会也可能不会断言,具体取决于是否return x发生复制省略.如果它确实发生,程序运行正常.如果没有发生,程序断言.

因此有理由:当允许RVO时,我们已经处于一个无法保证价值的区域x.所以我们应该能够利用这个余地并从中走出来x.风险看起来很小,利益看起来很大.这不仅意味着许多现有程序通过简单的重新编译会变得更快,但这也意味着我们现在可以从工厂函数返回"仅移动"类型.这对风险比率有很大的好处.

在标准化过程的后期,我们有点贪心,并且还说当返回一个by-value参数(并且类型与返回类型匹配)时会发生隐式移动.这里的好处似乎也相对较大,尽管代码破损的可能性略大,因为这不是RVO合法(或合法)的情况.但我没有为这种情况打破代码的演示.

因此,最终,您的核心问题的答案是,移动语义的原始设计在破坏现有代码方面采取了非常保守的路线.如果不是,它肯定会被委员会击落.在这个过程的后期,有一些变化使设计更具侵略性.但到目前为止,核心提案已在大多数(但并非一致)支持的标准中牢固确立.


mav*_*vam 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.
Run Code Online (Sandbox Code Playgroud)

在里面set_name调用赋值运算符std::string,它会产生不必要的副本.但也有一个右值超载operator=,这使你的情况更为合理:

void set_name(std::string v)
{
    name_ = std::move(v);
}
Run Code Online (Sandbox Code Playgroud)

这样,发生的唯一复制是字符串构造(std::string("Olaf")).

  • 扩展@ MooingDuck的答案:通过仅使用值语义,由*调用者*决定是否招致移动或复制. (3认同)