[英]C++ move-assignment prevents copy-swap idiom
在C ++中,copy-swap惯用法通常是这样实现的:
C& operator=(C rhs)
{
swap(*this, rhs);
return *this;
}
现在,如果我想添加一个移动赋值运算符,它应该看起来像这样:
C& operator=(C&& rhs)
{
swap(*this, rhs);
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;
}
我使用-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中的最后三个函数调用现在已经下降到单个函数调用。 这是预期的,因为我们不再按值传递赋值运算符参数,因此不会发生构造。
但是,我没有从赋值运算符中的复制交换习语的美感中受益。 任何见解都非常感谢。
如果提供有效的移动构造函数,实际上不需要实现移动赋值运算符。
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) 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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.