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

Yue*_*ang 6 c++ rvalue-reference assignment-operator move-semantics c++11

这是C++ Primer第5版的练习:

练习13.53:作为低效率的问题,HasPtr 赋值运算符并不理想.解释为什么.为HasPtr实现一个复制赋值和移动赋值运算符,并比较新移动赋值运算符和复制交换版本中执行的操作.(P.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;
};
Run Code Online (Sandbox Code Playgroud)

文件的一部分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;
} 
Run Code Online (Sandbox Code Playgroud)

我的问题是为什么这样做效率不高?

How*_*ant 11

步骤1

设置执行移动赋值运算符的性能测试.

设置另一个执行复制赋值运算符的性能测试.

第2步

按照问题陈述中的指示,以两种方式设置赋值运算符.

第3步

迭代步骤1和2,直到您确信已正确执行它们.

第3步应该可以帮助您了解正在发生的事情,最有可能的方法是告诉您性能在哪里发生变化以及变化的位置.

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

第4步

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

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

澄清

有两个特殊的成员赋值运算符,通常具有签名:

HasPtr& operator=(const HasPtr& rhs);  // copy assignment operator
HasPtr& operator=(HasPtr&& rhs);       // move assignment operator
Run Code Online (Sandbox Code Playgroud)

可以使用单个赋值运算符实现移动赋值和复制赋值,并使用所谓的复制/交换习惯用法:

HasPtr& operator=(HasPtr rhs);
Run Code Online (Sandbox Code Playgroud)

此单个赋值运算符不能使用第一个集重载.

使用复制/交换习惯用法实现两个赋值运算符(复制和移动)或仅使用一个是否更好?这就是练习13.53所要求的.要回答,您必须尝试两种方式,并测量复制分配和移动分配.聪明,善意的人通过猜测而不是测试/测量来弄错.你选择了一个很好的练习来学习.

  • 我喜欢使用C++ 11` <chrono>`工具.在本练习中,我将测试包装在`auto t0 = high_resolution_clock :: now();`和`auto t1 = high_resolution_clock :: now();`中,然后打印出经过的时间(以毫秒为单位):`std :: cout << duration_cast <毫秒>(t1-t0).count()<<"ms \n";`.上面假设一个`using namespace std :: chrono;`.没有它,将找不到我列出的几个标识符.如果你没有`auto`,请在其位置使用`high_resolution_clock :: time_point`. (5认同)
  • 这里有两个赋值运算符:复制赋值运算符和移动赋值运算符.通过值获取rhs的赋值运算符可以使用一个签名来实现.决定这是否是一个好主意是本练习的重点.您必须测试复制分配和移动分配以完全解决此练习. (2认同)
  • 我还在`t0`和`t1`之间放置了大量的复制/移动赋值,这样我就得到了非零的毫秒数.放置太少会导致测试运行更快,但结果会有更多变化.放置太多导致刺激性长的测试.这是一种正确的性能测试的艺术形式. (2认同)