- 除了定义拷贝控制成员,资源管理的类通常还定义一个名为swap的函数。(哪些类会调用swap的函数:那些与重排元素顺序的算法一起使用的类)。
- 如果一个类定义了自己的swap,那么算法将使用类自定义版本。否则,算法将使用标准库定义的swap。
编写我们自己的swap函数
- 类定义一个自己版本的swap来重载swap的默认行为。
- 与拷贝控制成员不同,swap并不是必要的。但是,对于分配了资源的类,定义swap可能是一种很重要的优化手段。
class HasPtr{
//swap定义为frined,以便能访问HasPtr的(private的)数据成员
friend void swap(HasPtr&,HasPtr&);
//其他成员定义
};
//swap的存在就是为了优化代码,将其声明为inline函数
inline void swap(HasPtr &lhs,HasPtr &rhs)
{
/*
swap的函数体对给定对象的每个数据成员调用swap。
在本例中,数据成员(ps,i)是内置类型的,而内置类型是没有特定版本的swap,
所以在本例中,对swap的调用会调用标准库std::swap。
*/
using std::swap;
swap(lhs.ps,rhs.ps);//交换指针,而不是string数据
swap(lhs.i,rhs.i);//交换int成员
}
swap 函数应该调用swap,而不是std::swap
如果一个类的成员有自己类型特定的swap函数,调用std::swap就是错误的了。
/*
假设Foo类有一个类型为HasPtr的成员h。
如果我们未定义Foo版本的swap,就会使用标准库版本的swap。
如例1,标准库swap对HasPtr管理的string进行了不必要的拷贝。
*/
//例1:会编译通过,但不建议这么写
void swap(Foo &lhs,Foo &rhs)
{
//错误:这个函数使用了标准库版本的swap,而不是HasPtr版本
std::swap(lhs.h,rhs.h);//标准库swap对HasPtr管理的string进行了不必要的拷贝。
//交换类型Foo的其他成员
}
/*
每个swap调用应该都是未加限定的。即
每个调用都应该是swap,而不是std::swap.
如果存在类型特定的swap版本,其匹配程度会优于std中定义的版本。
*/
//例2:正确的swap函数
void swap(Foo &lhs,Foo &rhs)
{
using std::swap;
swap(lhs.h,rhs.h);//使用HasPtr版本的swap
//交换类型Foo的其他成员
}
在赋值运算符中使用swap
- 定义swap的类通常用swap来定义它们的赋值运算符。这些运算符使用了一种名为拷贝并交换的技术。这种技术将左侧运算对象与右侧运算对象的一个副本进行交换。
- 拷贝并交换技术自动处理了自赋值情况且天然就是异常安全的。
/*
注意rhs是按值传递的,意味着HasPtr的拷贝构造函数将右侧运算对象中的string拷贝到rhs
*/
HasPtr& HasPtr::operator=(HasPtr rhs)//非引用参数,右侧运算对象以传值方式传递给赋值运算符。
{
//交换左侧运算对象和局部变量rhs的内容
swap(*this,rhs);//rhs现在指向本对象曾经使用的内存
return *this;//rhs被销毁,HasPtr的析构函数将执行,从而delete了rhs中的指针,即释放掉左侧运算对象中原来的内存
}
#include<iostream>
#include<cstring>
using namespace std;
class HasPtr {
friend void swap(HasPtr&, HasPtr&);
public:
/*
第一个构造函数接受一个(可选的)string参数。
这个构造函数动态分配它自己的string副本。并将指向string的指针保存在ps中。
*/
HasPtr(const string& s = string()) :ps(new string(s)), i(0) {}//第一个构造函数
//对于ps指向的string,每个HasPtr对象都有自己的拷贝
HasPtr(const HasPtr& p) :ps(new string(*p.ps)), i(p.i) { }//拷贝构造函数也分配它自己的string副本
HasPtr& operator=(const HasPtr&);//拷贝赋值运算符
HasPtr& operator= (const string&);//赋予新string
string& operator*();//解引用
~HasPtr();//析构函数对指针成员ps执行delete,释放析构函数中分配的内存。
private:
string* ps;
int i;
};
//swap的存在就是为了优化代码,将其声明为inline函数
inline void swap(HasPtr& lhs, HasPtr& rhs)
{
/*
swap的函数体对给定对象的每个数据成员调用swap。
在本例中,数据成员(ps,i)是内置类型的,而内置类型是没有特定版本的swap,
所以在本例中,对swap的调用会调用标准库std::swap。
*/
cout << "交换" << *lhs.ps << "和" << *rhs.ps << endl;
swap(lhs.ps, rhs.ps);//交换指针,而不是string数据
swap(lhs.i, rhs.i);//交换int成员
}
HasPtr::~HasPtr()
{
delete ps;//释放string内存
}
inline HasPtr& HasPtr::operator=(const HasPtr& rhs)
{
if (this == &rhs)//检测自我赋值 myadd
return *this;
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;
}
int main(int argc, char** argv)
{
HasPtr h("hi,mom!");
/*
如果未定义拷贝构造函数,在拷贝HasPtr对象时,合成的拷贝构造函数会建档复制ps成员,使得两个HasPtr指向相同的string。
当其中一个HasPtr修改string内容时,另一个HasPtr也被改变,这不符合我们的设想。
如果同时定义了析构函数,情况会更为糟糕,当销毁其中一个HasPtr时,ps指向的strig被销毁,另一个HasPtr的ps成为空悬指针。
*/
HasPtr h2(h);//调用拷贝构造函数;行为类值,h2,h3和h指向不同string
HasPtr h3 = h;//调用拷贝构造函数
h2 = "hi,dad!";
h3 = "hi,son!";
swap(h2,h3);
cout << "h:" << *h << endl;
cout << "h2:" << *h2 << endl;
cout << "h3:" << *h3 << endl;
return 0;
}
/*关于vector调用拷贝构造函数的原理*/
#include <iostream>
#include <cstdlib>
#include <vector>
using namespace std;
class Point
{
public:
Point()
{
cout << "construction" << endl;
}
Point(const Point& p)
{
cout << "copy construction" << endl;
}
~Point()
{
cout << "destruction" << endl;
}
};
int main()
{
vector<Point> pointVec;
Point a; //创建a会调用一次构造函数,释放时调用析构函数
Point b; //创建b会调用一次构造函数,释放时调用析构函数
pointVec.push_back(a);//vector会申请一个内存空间,并调用拷贝构造函数将a放到vector中
pointVec.push_back(b);//此时内存不够 ,需要扩大内存,重新分配内存,这时再调用拷贝构造函数将a拷贝到新的内存,再将b拷入新的内存,同时又调用Point拷贝构造函数,最后释放原来a的内存,此时调用Point的析构函数。
cout << pointVec.size() << std::endl;
//程序结束,pointVec包含两个元素a和b,故还会调用两次析构函数。
return 0;
}
============================================================
/************当main函数中n不超过16时,swap并未被调用**************/
#include<iostream>
#include<string>
#include <vector>
#include<algorithm>
//using namespace std;
using std::string;
using std::cout;
using std::endl;
using std::vector;
using std::to_string;
class HasPtr {
friend void swap(HasPtr&, HasPtr&);
public:
/*
第一个构造函数接受一个(可选的)string参数。
这个构造函数动态分配它自己的string副本。并将指向string的指针保存在ps中。
*/
HasPtr(const string& s = string()) :ps(new string(s)), i(0) { cout << "construction" << endl; }//第一个构造函数
//对于ps指向的string,每个HasPtr对象都有自己的拷贝
HasPtr(const HasPtr& p) :ps(new string(*p.ps)), i(p.i) { cout << "copy construction" << endl; }//拷贝构造函数也分配它自己的string副本
HasPtr& operator=(const HasPtr&);//拷贝赋值运算符
HasPtr& operator= (const string&);//赋予新string
string& operator*();//解引用
bool operator <(const HasPtr&) const;//比较运算
~HasPtr();//析构函数对指针成员ps执行delete,释放析构函数中分配的内存。
private:
string* ps;
int i;
};
HasPtr::~HasPtr()
{
cout << "destruction" << endl;
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;
}
//swap的存在就是为了优化代码,将其声明为inline函数
inline void swap(HasPtr &lhs, HasPtr &rhs)
{
/*
swap的函数体对给定对象的每个数据成员调用swap。
在本例中,数据成员(ps,i)是内置类型的,而内置类型是没有特定版本的swap,
所以在本例中,对swap的调用会调用标准库std::swap。
*/
using std::swap;
cout << "交换" << *lhs.ps << "和" << *rhs.ps << endl;
swap(lhs.ps, rhs.ps);//交换指针,而不是string数据
swap(lhs.i, rhs.i);//交换int成员
}
bool HasPtr::operator <(const HasPtr& rhs) const
{
return *ps < *rhs.ps;
}
int main(int argc, char** argv)
{
vector<HasPtr>vh;
int n = atoi("2");//把字符串转换成整型数
for (int i = 0; i < n; i++)
vh.push_back(to_string(n - i));//调用1次构造函数(创建新对象)、1次拷贝构造函数和1次析构函数;再调用1次构造函数(创建新对象)和2次拷贝构造函数,最后调用2次析构函数(n=2时)
for (auto p : vh)//会调用两次拷贝构造函数:原理是从vh中取一个内容赋值给变量p;然后在循环体中操作p;每循环一遍,执行一次析构函数(n=2时)
cout << *p << " ";
cout << endl;
// sort(首元素地址(必填),尾元素地址的下一个地址(必填),比较函数(非必填));如果不写比较函数,则默认对前面给出的区间进行递增排序。
sort(vh.begin(),vh.end());//先调用一次拷贝构造函数,再调用两次比较运算函数,再调用两次拷贝赋值运算符,最后再调用一次析构函数(n=2时)
for (auto p : vh)//调用两次拷贝构造函数,每循环一遍,执行一次析构函数(n=2时)
cout << *p << " ";
cout << endl;
return 0;
//程序结束,会再调用两次析构函数
}
/*********当main函数中n大于等于17时,经调试,swap也并未被调用,与答案不符***********/