除了定义拷贝控制成员,管理资源的类通常还定义了一个名为swap的函数。为了交换两个对象我们需要进行一次拷贝和两次赋值。
HasPtr temp = v1; //创建v1的值的一个临时副本
v1 = v2; //将v2的值赋予v1()
v2 = temp; //将保存的v1的值赋予v2
拷贝一个类值的HasPtr会分配一个新string并将其拷贝到HasPtr指向的位置,而这些拷贝中的内存分配都是不必要的。我们更希望swap交换指针,而不是分配string的新副本
string *temp = v1.ps; //为v1.ps中的指针创建一个副本
v1.ps = v2.ps; //将v2.ps中的指针赋予v1.ps
v2.ps = temp; //将保存的v1.ps中原来的指针赋予v2.ps
编写自己的swap函数
class HasPtr{
friend void swap(HasPtr&, HasPtr&);
};
inline swap(HasPtr &lhs, HasPtr &rhs){
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i, rhs.i);
}
如果一个类成员有自己的swap函数版本,就不能调用标准库的std::swap(),应该直接调用类型特定的swap函数
在赋值运算中使用swap
定义了swap函数的类,通常用swap来定义它们的赋值运算符,这些运算符使用了一种名为拷贝并交换(copy and swap)的技术。这种技术将左侧运算对象与右侧对象的一个副本进行交换:
//注意rhs是按值传递的,意味着HasPtr的拷贝构造函数将右侧运算对象中的string拷贝到rhs
HasPtr& HasPtr::operator=(HasPtr rhs){
//交换左侧运算对象和局部变量rhs的内容
swap(*this, rhs);
return *this;
}
该版本中,并非是引用传递,而是值传递,rhs是右侧运算对象的一个副本,参数传递时拷贝HasPtr的操作会分配该对象string的一个新副本。
在swap调用之后,*this中的指针成员指向新分配的string——右侧运算对象中string的一个副本。当赋值运算符结束时,rhs被销毁,HasPtr中的析构函数被执行。此析构函数delete rhs现在指向的内存,即,释放掉左侧运算对象中原来的内存。
这个技术自动处理了自赋值的情况,它通过在改变左侧运算对象之前拷贝右侧对象保证了字赋值的正确。
解释:swap(HasPtr&, HasPtr&)中对swap的调用不会导致递归循环
解答:在swap函数中又调用了swap来交换ps和i,但这两个类型为指针和整型,属于内置类型。因此函数调用中的swap调用被解析为std::swap,而不是HasPtr特定版本的swap函数,所以不会导致递归调用。
为你的类值版本的HasPtr类编写swap函数
inline void swap(HasPtr &lhs, HasPtr &rhs){
cout << " 交换 " << *lhs.ps << " 和 " << *rhs.ps << endl;
swap(lhs.ps, rhs.ps); //交换指针
swap(lhs.i, rhs.i); //交换int成员
}
为你的HasPtr类定义一个<运算符,并定义一个HasPtr的vector。为这个vector添加一些元素,并对它执行sort。注意何时会调用swap
<运算符直接返回两个HasPtr的ps指向的string的比较结果即可,但需要注意的是,它应该被声明为const。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
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); //交换指针
swap(lhs.i, rhs.i); //交换int成员
}
bool HasPtr::operator<(const HasPtr& rhs) const {
return *ps < *rhs.ps;
}
int main() {
vector<HasPtr> v;
int n = atoi(__argv[1]);
for (int i = 0; i < n; i++)
v.push_back(to_string(n - i));
for (auto p : v) {
cout << *p << " ";
}
cout << endl;
sort(v.begin(), v.end());
for (auto p : v) {
cout << *p << " ";
}
cout << endl;
return 0;
}
类指针的HasPtr版本会从swap函数中受益吗?
默认的swap版本简单交换两个对象的非静态成员,对于HasPtr来言,就是交换string指针ps、引用计数指针use和整型值i。因此,默认的swap版本能够正确处理类指针HasPtr的交换,专用swap版本不会有更多受益。