C ++移动分配可防止复制交换习惯用法

时间:2018-04-12 12:13:39

标签: c++ c++11 move-semantics

在C ++中,copy-swap惯用法通常是这样实现的:

C& operator=(C rhs)
{
    swap(*this, rhs);
    return *this;
}

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

C& operator=(C&& rhs)
{
    swap(*this, rhs);
    return *this;
}

但是,这会产生关于应该调用哪个赋值运算符以及编译器正确地抱怨它的模糊性。所以我的问题如下:如果我想支持copy-swap惯用语和移动赋值语义我应该做什么?

或者这是一个非问题,因为有一个移动复制构造函数和复制交换习惯用法,有一个并没有真正受益于移动赋值运算符?

在提出这个问题后,我编写了一个代码,证明移动分配可能导致比复制交换习惯用户更少的函数调用。首先让我提出我的复制交换版本。请多多包涵;它似乎只是一个很长但很简单的例子:

#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;
}

我使用-fno-elide-constructors选项用g ++编译了这个,因为我想看到无优化行为。结果如下:

sz = 4
C(data) called   // (due to the declaration of result)
C() called       // (called from the rvalue copy-constructor)
C(&&rhs) called  // (called due to copy to return temporary)
C() called       // (called from the rvalue copy-constructor)
C(&&rhs) called  // (called due to pass-by-value in the assignment operator)
operator= called

现在,如果我选择不在赋值运算符中创建copy-swap惯用语,我会这样:

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;
}

这允许我使用move-assignment运算符,如下所示:

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

    swap(*this, rhs);

    return *this;
}

现在,在其他所有内容相同的情况下,我得到以下输出:

sz = 4
C(data) called        // (due to the declaration of result)
C() called            // (called from the rvalue copy-constructor)
C(&&rhs) called       // (called due to copy to return temporary)
operator=(C&&) called // (move-assignment)

正如您所看到的,这会导致更少的函数调用。实际上,copySwapIdiom中的最后三个函数调用现在已经下降到单个函数调用。这是预期的,因为我们不再按值传递赋值运算符参数,因此不会发生构造。

但是,我并没有从赋值运算符中的复制交换习语中获益。非常感谢任何见解。

2 个答案:

答案 0 :(得分:6)

如果提供有效的移动构造函数,实际上不需要实现移动赋值运算符。

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;
    }

答案 1 :(得分:4)

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

C& operator=(C rhs) noexcept;

表示替换该对

C& operator=(const C& rhs);
C& operator=(C&& rhs) noexcept;

C& operator=(C rhs) noexcept;执行复制或移动分配取决于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