在 C++ 中,复制交换惯用法通常是这样实现的:
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
现在,如果我选择不在赋值运算符中使用复制交换习惯用法,我将得到如下内容:
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& 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 中的最后三个函数调用现在已降至单个函数调用。这是预期的,因为我们不再按值传递赋值运算符参数,因此那里不会发生构造。
然而,我并没有受益于赋值运算符中复制交换习惯用法的美妙之处。任何见解都非常感激。