《C++ Primer》第13章 拷贝控制
13.3节 交换操作 习题答案
练习13.29:解释swap(HasPtr&, HasPtr&)中对swap的调用不会导致递归循环。
【出题思路】
理解swap调用的确定。
【解答】
在此swap函数中又调用了swap来交换HasPtr成员ps和i。但这两个成员的类型分别是指针和整型,都是内置类型,因此函数中的swap调用被解析为std::swap,而不是HasPtr的特定版本swap(也就是自身),所以不会导致递归循环。
练习13.30:为你的类值版本的HasPtr编写swap函数,并测试它。为你的swap函数添加一个打印语句,指出函数什么时候执行。
【出题思路】
练习定义swap。
【解答】
参考书中本节内容即可完成swap函数的编写。swap函数和主函数如下所示:
#include <iostream>
#include <string>
using namespace std;
class HasPtr
{
friend void swap(HasPtr &lhs, HasPtr &rhs);
public:
//构造函数分配新的string和新的计数器,将计数器置为1
HasPtr(const string &s = string())
:ps(new string(s)), i(0), use(new size_t(1))
{
}
//拷贝构造函数拷贝所有三个数据成员,并递增计数器
HasPtr(const HasPtr &p)
:ps(p.ps), i(p.i), use(p.use) //拷贝构造函数
{
++*use;
}
HasPtr& operator=(const HasPtr&); //拷贝构造函数
HasPtr& operator=(const string&); //拷贝赋值运算符
string& operator*(); //解引用
~HasPtr();
private:
string *ps;
int i;
size_t *use; //用来记录有多少个对象共享*ps的成员
};
HasPtr::~HasPtr()
{
if(--*use == 0) //如果引用计数变为0
{
delete ps; //释放string内存
delete use; //释放计数器内存
}
}
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
++*rhs.use; //递增右侧运算对象的引用计数
if(--*use == 0) //然后递减本对象的引用计数
{
delete ps; //如果没有其他用户
delete use; //释放本对象分配的成员
}
ps = rhs.ps; //将数据从rhs拷贝到本对象
i = rhs.i;
use = rhs.use;
return *this; //返回本对象
}
HasPtr& HasPtr::operator=(const string &rhs)
{
*ps = rhs;
return *this;
}
inline void swap(HasPtr &lhs, HasPtr &rhs)
{
cout << "交换 " << *lhs.ps << "和" << *rhs.ps << endl;
swap(lhs.ps, rhs.ps); //交换指针,而不是string数据
swap(lhs.i, rhs.i); //交换int成员
}
string& HasPtr::operator*()
{
return *ps;
}
int main(int argc, const char * argv[])
{
HasPtr h("hi Tom!");
HasPtr h2(h);//行为类值,h2, h3和h指向不同string
cout << "old h: " << *h << endl;//调用HasPtr::operator*()
cout << "old h2: " << *h2 << endl;//调用HasPtr::operator*()
HasPtr h3 = h;
cout << "1 h: " << *h << endl;//调用HasPtr::operator*()
cout << "1 h2: " << *h2 << endl;//调用HasPtr::operator*()
cout << "1 h3: " << *h3 << endl;//调用HasPtr::operator*()
h2 = "hi Cmale!";//调用operator=(const string &rhs)这个函数,h, h2,h3都是“hi daa!”
cout << "2 h: " << *h << endl;//调用HasPtr::operator*()
cout << "2 h2: " << *h2 << endl;//调用HasPtr::operator*()
cout << "2 h3: " << *h3 << endl;//调用HasPtr::operator*()
h3 = "hi Rattbt!";//调用operator=(const string &rhs)这个函数,h, h2,h3都是“hi son!”
cout << "3 h: " << *h << endl;//调用HasPtr::operator*()
cout << "3 h2: " << *h2 << endl;//调用HasPtr::operator*()
cout << "3 h3: " << *h3 << endl;//调用HasPtr::operator*()
swap(h2, h3);
cout << "swap h: " << *h << endl;//调用HasPtr::operator*()
cout << "swap h2: " << *h2 << endl;//调用HasPtr::operator*()
cout << "swap h3: " << *h3 << endl;//调用HasPtr::operator*()
std::cout << "Hello, World!\n";
return 0;
}
运行结果:
练习13.31:为你的HasPtr类定义一个<运算符,并定义一个HasPtr的vector。为这个vector添加一些元素,并对它执行sort。注意何时会调用swap。
【出题思路】
理解swap的应用。
【解答】
<运算符直接返回两个HasPtr的ps指向的string的比较结果即可,但需要注意的是,它应被声明为const的。
值得注意的是,在tdm-gcc 4.8.1的STL实现中,当元素数小于等于16时,sort使用的是插入排序算法,且未使用swap交换元素,而是内存区域的整片移动。因此,当我们将命令行参数设定为不超过16时(本程序接受唯一的命令行参数表示HasPtr对象数目),会发现swap并未被调用(没有相应输出)。而当命令行参数大于等于17时,就会发现sort调用了swap。因为此时sort采用的是快速排序算法,使用了swap交换元素。但你会发现,交换元素的次数可能比你预期的快速排序算法的交换元素次数少得多。原因是,在快速排序算法递归排序的过程中,如果子序列长度小于等于16,又会转到插入排序算法,而不会继续递归直至子序列长度变为1。
读者可编译运行程序,设置命令行参数,观察sort过程中是如何交换元素的。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using std::string;
using std::cout;
using std::endl;
using std::vector;
using std::to_string;
class HasPtr
{
friend void swap(HasPtr&, HasPtr&);
public:
HasPtr(const string &s = string())
:ps(new string(s)), i(0)
{
}
HasPtr(const HasPtr &p)
:ps(new string(*p.ps)), i(p.i) //拷贝构造函数
{
}
HasPtr& operator=(const HasPtr&); //拷贝赋值运算符
HasPtr& operator=(const string&); //赋予新string
string& operator*(); //解引用
bool operator<(const HasPtr&) const;//比较运算
~HasPtr();
private:
string *ps;
int i;
};
HasPtr::~HasPtr()
{
delete ps; //释放string内存
}
inline HasPtr& HasPtr::operator=(const HasPtr& rhs)
{
auto newps = new string(*rhs.ps); //拷贝指针指向的对象
delete ps; //销毁原string
ps = newps; //指向新string
i = rhs.i; //使用内置的int赋值
return *this; //返回一个此对象的引用
}
HasPtr& HasPtr::operator=(const string &rhs)
{
*ps = rhs;
return *this;
}
string& HasPtr::operator*()
{
return *ps;
}
inline void swap(HasPtr &lhs, HasPtr &rhs)
{
using std::swap;
cout << "交换" << *lhs.ps << "和" << *rhs.ps << endl;
swap(lhs.ps, rhs.ps);//交换指针,而不是string数据
swap(lhs.i, rhs.i);
}
bool HasPtr::operator<(const HasPtr& rhs) const
{
return *ps < *rhs.ps;
}
int main(int argc, const char * argv[])
{
vector<HasPtr> vh;
int n = atoi(argv[1]);//把字符串转换为整型数
for(int i = 0; i < n; i++)
{
vh.push_back(to_string(n - i));
}
for(auto p:vh)
{
cout << *p << " ";
}
cout << endl;
sort(vh.begin(), vh.end());
for(auto p: vh)
{
cout << *p << " ";
}
cout << endl;
std::cout << "Hello, World!\n";
return 0;
}
设置命令行参数
运行结果:
练习13.32:类指针的HasPtr版本会从swap函数受益吗?如果会,得到了什么益处?如果不是,为什么?
【出题思路】
深入理解swap和类指针的类。
【解答】
默认swap版本简单交换两个对象的非静态成员,对HasPtr而言,就是交换string指针ps、引用计数指针use和整型值i。可以看出,这种语义是符合期望的——两个HasPtr指向了原来对方的string,而两者互换string后,各自的引用计数本应该是不变的(都是减1再加1)。因此,默认swap版本已经能正确处理类指针HasPtr的交换,专用swap版本不会带来更多收益。