C++ Primer 5th中练习13.53-copy and swap Vs copy/move assignment

C++ Primer 5th中练习13.53

Exercise 13.53: As a matter of low-level efficiency, the HasPtr assignment operator is not ideal. Explain why. Implement a copy-assignment and move assignment operator for HasPtr and compare the operations executed in your new move-assignment operator versus the copy-and-swap version.

.h文件:

#ifndef _HASPTR_HH
#define _HASPTR_HH

#include <string>
#include <iostream>

//! 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
    // copy-and-swap
    HasPtr&
    operator= (HasPtr rhs);

    //! destructor.
    ~HasPtr()
    {
        delete ps;
    }

private:
    std::string *ps;
    int    i;
};

#endif // _HASPTR_HH

.cpp文件

#include "HasPtr.hh"

#include <iostream>

//! 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
}

//! operator = using specific swap
// copy-and-swap
HasPtr&
HasPtr::operator= (HasPtr rhs) {
    swap(*this,rhs);
    return *this;
}

测试代码

#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <algorithm>

#include <set>
#include <chrono>
#include <utility>
#include "String.hh"
#include "HasPtr.hh"

using namespace std;
int main() {
    HasPtr hp1 {"111111111111111234567876548765787656787654567854566543451"}, hp2;

    // 记录开始时间
    auto start = std::chrono::high_resolution_clock::now();
    for(int i = 0; i < 1000000; ++i)
        // copy-and-swap
        // copy constructor + assignment
        // hp2 = hp1; // 执行时间: 0.133841

        // move constructor + assignment
        hp2 = std::move(hp1); // 执行时间: 0.0202316

     // 记录结束时间
    auto end = std::chrono::high_resolution_clock::now();

    // 计算耗时
    std::chrono::duration<double> elapsed = end - start;
    std::cout << "执行时间: " << elapsed.count() << std::endl;
    /*
    g++ -fno-elide-constructors -std=c++11 -Wall -o main main.cc HasPtr.cc
    */

    return 0;
}

实现copy assignmentmove assignment
.h文件:

#ifndef _HASPTR_HH
#define _HASPTR_HH

#include <string>
#include <iostream>

//! 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
    // copy-and-swap
    // HasPtr&
    // operator= (HasPtr rhs);

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

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


    //! destructor.
    ~HasPtr()
    {
        delete ps;
    }

private:
    std::string *ps;
    int    i;
};

#endif // _HASPTR_HH

.cpp文件

#include "HasPtr.hh"

#include <iostream>

//! 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
}

//! operator = using specific swap
// copy-and-swap
// HasPtr&
// HasPtr::operator= (HasPtr rhs) {
//     swap(*this,rhs);
//     return *this;
// }

// copy assignment
HasPtr&
HasPtr::operator= (const HasPtr &rhs) {
    std::string *newData = new std::string(*rhs.ps);

    delete ps;
    ps = newData;
    i = rhs.i;
    return *this;
}

// move assignment
HasPtr&
HasPtr::operator= (HasPtr &&rhs) {
    if(this != &rhs) {
        ps = rhs.ps;
        i = rhs.i;

        rhs.ps = nullptr;
    }
    return *this;
}

测试代码:

#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <algorithm>

#include <set>
#include <chrono>
#include <utility>
#include "String.hh"
#include "HasPtr.hh"

using namespace std;
int main() {
    HasPtr hp1 {"111111111111111234567876548765787656787654567854566543451"}, hp2;

    // 记录开始时间
    auto start = std::chrono::high_resolution_clock::now();
    for(int i = 0; i < 1000000; ++i)
        // copy-and-swap
        // copy constructor + assignment
        // hp2 = hp1;           // 执行时间: 0.133841

        // move constructor + assignment
        // hp2 = std::move(hp1); // 执行时间: 0.0202316

        // copy assignment
        // hp2 = hp1;              // 执行时间: 0.124041
        // move assignment
        hp2 = std::move(hp1); // 执行时间: 0.00637096
     // 记录结束时间
    auto end = std::chrono::high_resolution_clock::now();

    // 计算耗时
    std::chrono::duration<double> elapsed = end - start;
    std::cout << "执行时间: " << elapsed.count() << std::endl;
    /*
    g++ -fno-elide-constructors -std=c++11 -Wall -o main main.cc HasPtr.cc
    */

    return 0;
}

所以总结一下:

copy-and-swap和copy assignment差不多

  1. copy-and-swap和copy assignment差不多,其实copy-and-swap多跑几次的结果是:

    [Chap1] : ) ./main
    执行时间: 0.140264
    [Chap1] : ) ./main
    执行时间: 0.141963
    [Chap1] : ) ./main
    执行时间: 0.130669
    [Chap1] : ) ./main
    执行时间: 0.142128
    [Chap1] : ) ./main
    执行时间: 0.133721
    [Chap1] : ) ./main
    执行时间: 0.13198
    [Chap1] : ) ./main
    执行时间: 0.136442
    [Chap1] : ) ./main
    执行时间: 0.13367

    而copy assignment多跑几次的结果是:

    [Chap1] : ) ./main
    执行时间: 0.130349
    [Chap1] : ) ./main
    执行时间: 0.120378
    [Chap1] : ) ./main
    执行时间: 0.122632
    [Chap1] : ) ./main
    执行时间: 0.121985
    [Chap1] : ) ./main
    执行时间: 0.124312
    [Chap1] : ) ./main
    执行时间: 0.122294
    [Chap1] : ) ./main
    执行时间: 0.123275
    [Chap1] : ) ./main
    执行时间: 0.123618
    [Chap1] : ) ./main
    执行时间: 0.123131

    所以copy-assignment比copy-and-swap是客观上的快(如果算法精度有这差距不得水篇文章?)
    根本原因目前我不知,但是我们可以插入一些输出语句来分析一下。首先copy-and-swap它需要调用
    copy constructor来用hp1初始化rhs,然后才执行copy-and-swap的函数体。而copy- assignment直接 执行其函数体。客观上两者都进行了一次copy,前者发生在copy constructor的初始化列表里(ps(new std::string(*hp.ps))),后者发生在copy- assignment的函数体内(std::string *newData = new std::string(*rhs.ps);),然后前者还要在copy-and-swap函数体内执行swap操作(swap(*this,rhs);),但是后者只进行了赋值操作( ps = newData; i = rhs.i;)。难道是因为swap比赋值开销大一些?不过总体性能是差不多的。

copy-and-swap比move assignment肉眼可见的慢

  1. copy-and-swap比move assignment肉眼可见的慢,如果前者多跑几次的话:

    [Chap1] : ) ./main
    执行时间: 0.0214209
    [Chap1] : ) ./main
    执行时间: 0.0156422
    [Chap1] : ) ./main
    执行时间: 0.0196867
    [Chap1] : ) ./main
    执行时间: 0.0143008
    [Chap1] : ) ./main
    执行时间: 0.0143276
    [Chap1] : ) ./main
    执行时间: 0.0190589
    [Chap1] : ) ./main
    执行时间: 0.014424
    [Chap1] : ) ./main
    执行时间: 0.0197936
    [Chap1] : ) ./main
    执行时间: 0.0188477

    ,而move assignment多跑几次的话:

    [Chap1] : ) ./main
    执行时间: 0.00741979
    [Chap1] : ) ./main
    执行时间: 0.00479204
    [Chap1] : ) ./main
    执行时间: 0.00680325
    [Chap1] : ) ./main
    执行时间: 0.00676896
    [Chap1] : ) ./main
    执行时间: 0.00675975
    [Chap1] : ) ./main
    执行时间: 0.00691304
    [Chap1] : ) ./main
    执行时间: 0.00686121
    [Chap1] : ) ./main
    执行时间: 0.00430117

    所以这里的差距是哪里造成的呢?首先copy-and-swap需要调用move constructor,然后才会执行其函数体内的swap(*this,rhs);。有趣的是, move assignment做的就是move constructor的事情,所以前者硬生生比后者多执行了一个swap(*this,rhs);~~。

以上都是根据结果进行的猜测,真实原因不知。

参考

https://stackoverflow.com/questions/21010371/why-is-it-not-efficient-to-use-a-single-assignment-operator-handling-both-copy-a

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ocodotial

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值