为什么同时使用一个赋值运算符处理复制和移动赋值效率不高

Why is it not efficient to use a single assignment operator handling both copy and move assignment?

本文关键字:复制 处理 移动 赋值 效率 赋值运算符 一个 为什么      更新时间:2023-10-16

以下是C++Primer第五版:的练习

练习13.53:作为低效率的问题,HasPtr赋值运算符并不理想。解释原因。实施复制分配并移动HasPtr的分配运算符并进行比较在新的移动分配操作符中执行的操作与复制和交换版本。(第544页)

文件hasptr.h:

//! a class holding a std::string*
class HasPtr
{
friend void swap(HasPtr&, HasPtr&);
friend bool operator <(const HasPtr& lhs, const HasPtr& rhs);
public:
//! default constructor.
HasPtr(const std::string &s = std::string()):
ps(new std::string(s)), i(0)
{ }
//! copy constructor.
HasPtr(const HasPtr& hp) :
ps(new std::string(*hp.ps)), i(hp.i)
{ }
//! move constructor.
HasPtr(HasPtr&& hp) noexcept :
ps(hp.ps), i(hp.i)
{ hp.ps = nullptr; }
//! assignment operator
HasPtr&
operator = (HasPtr rhs);
//! destructor.
~HasPtr()
{
delete ps;
}
private:
std::string *ps;
int    i;
};

文件hasptr.cpp:的一部分

//! specific swap.
inline void
swap(HasPtr &lhs, HasPtr &rhs)
{
using std::swap;
swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
swap(lhs.i, rhs.i);   // swap the int members
std::cout <<"swapping!n";
}
//! operator = using specific swap
HasPtr&
HasPtr::operator = (HasPtr rhs)
{
swap(*this,rhs);
return *this;
} 

我的问题是,为什么这样做没有效率?

步骤1

设置一个性能测试,练习移动分配运算符。

设置另一个性能测试,练习复制分配运算符。

步骤2

按照问题陈述中的说明,双向设置赋值运算符。

步骤3

重复执行步骤1和2,直到您确信自己做得正确为止。

步骤3应该有助于教育您了解正在发生的事情,很可能是告诉您性能在哪里发生了变化在哪里没有发生变化。

猜测不是步骤1-3的选项。实际上你必须去做。否则,你将(正确地)不相信你的猜测是正确的。

步骤4

现在您可以开始猜测了。有些人会称之为"形成假设"。这是"猜测"的一种奇特说法。但至少现在它是受过教育的猜测。

在回答这个问题时,我完成了这个练习,并注意到一次测试没有显著的性能差异,另一次测试的性能差异为6X。这进一步使我得出了一个假设。完成这项工作后,如果您不确定自己的假设,请使用代码、结果和后续问题更新您的问题。

澄清

有两种特殊的成员分配运算符,它们通常具有签名:

HasPtr& operator=(const HasPtr& rhs);  // copy assignment operator
HasPtr& operator=(HasPtr&& rhs);       // move assignment operator

可以使用一个具有所谓复制/交换习惯用法的分配运算符来实现移动分配和复制分配:

HasPtr& operator=(HasPtr rhs);

第一个集合不能重载此单个赋值运算符。

使用复制/交换习惯用法实现两个赋值运算符(复制和移动),还是只实现一个更好?这就是练习13.53的要求。若要回答,您必须同时尝试两种方法,并测量复制作业和移动作业。聪明、善意的人会通过猜测而不是测试/测量来犯错。你选择了一个好的练习来学习。

正如问题所表明的,这是"低效率的问题"。当您使用HasPtr& operator=(HasPtr rhs)并编写类似hp = std::move(hp2);的内容时,ps成员会被复制两次(指针本身不是它所指向的对象):一次是由于调用move构造函数而从hp2复制到rhs,一次是因为调用swap而从rhs复制到*this。但是当您使用HasPtr& operator=(HasPtr&& rhs)时,ps只从rhs复制到*this一次。