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 assignment
和move 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差不多
-
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肉眼可见的慢
-
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);
~~。