C++;移动分配防止复制交换习惯用法 在C++中,复制交换习惯用法通常是这样执行的: C& operator=(C rhs) { swap(*this, rhs); return *this; } C& operator=(C&& rhs) { swap(*this, rhs); return *this; } C& operator=(const C& rhs) { if (printOutput) { cout << "operator=(const C&) called" << endl; } if (this != &rhs) { delete data; data = new int; *data = *(rhs.data); } return *this; }

C++;移动分配防止复制交换习惯用法 在C++中,复制交换习惯用法通常是这样执行的: C& operator=(C rhs) { swap(*this, rhs); return *this; } C& operator=(C&& rhs) { swap(*this, rhs); return *this; } C& operator=(const C& rhs) { if (printOutput) { cout << "operator=(const C&) called" << endl; } if (this != &rhs) { delete data; data = new int; *data = *(rhs.data); } return *this; },c++,c++11,move-semantics,C++,C++11,Move Semantics,现在,如果我想添加一个移动赋值操作符,它应该是这样的: C& operator=(C rhs) { swap(*this, rhs); return *this; } C& operator=(C&& rhs) { swap(*this, rhs); return *this; } C& operator=(const C& rhs) { if (printOutput) { co

现在,如果我想添加一个移动赋值操作符,它应该是这样的:

C& operator=(C rhs)
{
    swap(*this, rhs);
    return *this;
}
C& operator=(C&& rhs)
{
    swap(*this, rhs);
    return *this;
}
C& operator=(const C& rhs)
{
    if (printOutput)
    {
        cout << "operator=(const C&) called" << endl;
    }

    if (this != &rhs)
    {
        delete data;

        data = new int;
        *data = *(rhs.data);
    }

    return *this;
}
然而,这就产生了关于应该调用哪个赋值运算符的模糊性,编译器有理由对此进行抱怨。所以我的问题是:如果我想支持复制交换习惯用法和移动分配语义,我应该怎么做

或者这不是一个问题,因为有了移动复制构造函数和复制交换习惯用法,移动赋值操作符并没有真正的好处

在问了这个问题之后,我编写了一段代码,演示了移动赋值可能比复制交换习惯用法导致更少的函数调用。首先让我介绍一下我的副本交换版本。请容忍我;这似乎是一个很长但很简单的例子:

#include <algorithm>
#include <iostream>
#include <new>

using namespace std;

bool printOutput = false;

void* operator new(std::size_t sz)
{
    if (printOutput)
    {
        cout << "sz = " << sz << endl;
    }

    return std::malloc(sz);
}

class C
{
    int* data;

    public:

    C() : data(nullptr)
    {
        if (printOutput)
        {
            cout << "C() called" << endl;
        }
    }

    C(int data) : data(new int)
    {
        if (printOutput)
        {
            cout << "C(data) called" << endl;
        }

        *(this->data) = data;
    }

    C(const C& rhs) : data(new int)
    {
        if (printOutput)
        {
            cout << "C(&rhs) called" << endl;
        }

        *data = *(rhs.data);
    }

    C(C&& rhs) : C()
    {
        if (printOutput)
        {
            cout << "C(&&rhs) called" << endl;
        }

        swap(*this, rhs);
    }

    C& operator=(C rhs)
    {
        if (printOutput)
        {
            cout << "operator= called" << endl;
        }

        swap(*this, rhs);

        return *this;
    }

    C operator+(const C& rhs)
    {
        C result(*data + *(rhs.data));

        return result;
    }

    friend void swap(C& lhs, C& rhs);

    ~C()
    {
        delete data;
    }
};

void swap(C& lhs, C& rhs)
{
    std::swap(lhs.data, rhs.data);
}

int main()
{
    C c1(7);
    C c2;

    printOutput = true;

    c2 = c1 + c1;

    return 0;
}
现在,如果我选择不在赋值运算符中使用复制交换习惯用法,我将得到如下结果:

C& operator=(C rhs)
{
    swap(*this, rhs);
    return *this;
}
C& operator=(C&& rhs)
{
    swap(*this, rhs);
    return *this;
}
C& operator=(const C& rhs)
{
    if (printOutput)
    {
        cout << "operator=(const C&) called" << endl;
    }

    if (this != &rhs)
    {
        delete data;

        data = new int;
        *data = *(rhs.data);
    }

    return *this;
}
正如您所看到的,这会导致更少的函数调用。实际上,copySwapIdiom中的最后三个函数调用现在已下降到一个函数调用。这是预期的,因为我们不再按值传递赋值运算符参数,因此不会在那里进行构造


然而,我并没有从赋值运算符中复制交换习惯用法的美妙之处中获益。非常感谢您的任何见解。

如果您提供了有效的移动构造函数,那么实际上不需要实现移动分配操作符

class Foo
{
public:
   explicit Foo(Bar bar)
       : bar(bar)
    { }

    Foo(const Foo& other)
        : bar(other.bar)
    { }

    Foo(Foo&& other)
        : bar(other.bar)
    { }

    // other will be initialized using the move constructor if the actual
    // argument in the assignment statement is an rvalue
    Foo& operator=(Foo other)
    {
        std::swap(bar, other.bar);
        return *this;
    }

复制交换习惯用法背后的动机是将复制/移动工作转发给构造函数,这样就不会同时复制构造函数和赋值运算符的工作。也就是说

C& operator=(C rhs) noexcept;
表示替换该对

C& operator=(const C& rhs);
C& operator=(C&& rhs) noexcept;
是否
C&operator=(C-rhs)无异常执行复制或移动分配取决于
rhs
的构造方式。比如说,

a = std::move(b); // rhs is move-constructed from r-value std::move(b), and thus move-assignment
c = d;            // rhs is copy-constructed from l-value d, and thus copy-assignment

“通常是这样实现的”-为了避免编写两个重载,一个用于移动右值,另一个用于复制LVLAUE…简短回答:使用移动语义。这就是它的作用。
C&operator=(C-rhs)
是一个移动赋值操作符。通过值传递的对象可以从右值初始化。你不需要添加任何其他内容。我收到的最佳答案是M.M.对我错误创建的答案的评论。我正在将其复制/粘贴到此处,以免丢失:“这应该是问题的一部分。虽然我会为您节省一些时间,并说:是的,复制交换习惯用法涉及的构造函数调用比使用单独的复制赋值和移动赋值运算符要多。在性能和代码简单性之间进行权衡。stackoverflow.com/questions/3279543–M.M上对此进行了更深入的讨论“事实上,这是使用复制和交换的主要原因之一:您将所有复杂行为包含在尽可能少的函数中,而不必将其复制到多个函数中。我不明白这一点。程序如何知道对象是如何构造的?@EduardRostomy更新了答案以澄清。