资源管理
资源管理
联想自动释放单例模式对象所使用的第一种方法,即再定义一个类来托管该资源,通过析构函数在销毁时自动调用的特性来实现自动回收资源的要求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 class SafeFile{
public:
SafeFile(File *fp)
:_fp(fp)
{}
~SafeFile(){
if(_fp){
fclose(_fp);
}
}
private:
FILE *_fp;
};既然通过该类把资源包装了,也要提供一些访问其资源的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 class SafeFile{
public:
SafeFile(File *fp)
:_fp(fp)
{}
~SafeFile(){
if(_fp){
fclose(_fp);
}
}
//提供一些访问资源的方法
//对文件进行写操作
void write(const string &msg){
fwirte(msg.c_str(),1,mes.size(),_fp);
}
private:
FILE *_fp;
};
void test0(){
string msg = "hello,world";
SafeFile sf(fopen("wd.txt","a+"));
sf.write(msg);
}
RAII技术
该技术本质上就是利用对象的生命周期来管理资源,因为对象的生命周期结束时,会自动调用析构函数
RAII类的常见特征:
在构造函数中初始化资源,或托管资源;(再给构造函数传参时初始化资源)
在析构函数中释放资源;
一般不允许进行复制或者赋值(对象语义);
提供若干访问资源的方法(如:读写文件)。
对象语义
与对象语义相反的就是值语义
值语义:可以进行复制或赋值(两个变量的值可以相同)
1
2
3
4
5 int a = 10; int b = a; int c = 20;
c = a; //赋值
int d = c; //复制对象语义:不允许复制或者赋值
(全世界不会有两个完全一样的人,程序世界中也不会有两个完全一样的对象)
常用手段:
- 将拷贝构造函数与赋值运算符函数设置为私有的
- 将拷贝构造函数与赋值运算符函数=delete
- 使用继承的思想,将基类的拷贝构造函数与赋值运算符函数删除(或设为私有),让派生类继承基类。
可以尝试用类模板来尝试实现RAII技术
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 template <class T>
class RaII{
public:
RAII(T *data)
:_data(data)
{}
`RAII(){
if(_data){
delete _data;
_data = nullptr;
}
}
RAII(const RAII & rhs) = delete;
RAII & operator=(const RAII &rhs) = delete;
//提供若干访问资源的方法
T operator->(){
return _data;
}
T& operator*(){
return *_data;
}
T *get() const{
return _data;
}
//重新管理另一片空间
void set(T *data){
if(_data){
delete _data;
_data = nullptr;
}
_data = data;
}
private:
T *_data;
};
void test(){
RAII<int> ra(new int(20));
//但是这样可能会有double free 的问题
int * p =new int(10);
RAII<int> ra(p);
delete p;//double free
}
智能指针
位于头文件中
,他们都是类模板
auto_ptr的使用
auto_ptr是最简单的智能指针,使用上存在缺陷,已经被C++17弃用了。
unique_ptr的使用*
unique_ptr对auto_prt进行了改造
特点一:不允许复制或赋值
具有对象语义
特点二:独享所有权的智能指针
1
2
3
4
5
6
7 int *p = new int(1);
unique_ptr<int> up(p);
cout << *up << endl;
cout << *p << endl;
//这两管理的是同一片资源吗,只需要运行看看有没有内存泄漏,有泄漏说明不是一片空间,反之说明是一片空间
//结果是一片空间,独享所有权是指只能由一个Unique_ptr管理这片空间
特点三:作为容器元素
要利用移动语义的特点,可以直接传递unique_ptr的右值作为容器的元素。如果传入左值形态的unique_ptr,会进行复制操作,而unique_ptr是不能复制的。
1
2
3
4
5
6
7
8
9
10
11
12 class Point{
...
};
unique_ptr<Point> up(new Point(1,2));
unique_ptr<Point> up2(new Point(2,3));
vector<unique_ptr<Point>> vec;
vec.push_back(up);//error 报错说已经删除的拷贝构造被调用了,因为此时的up 和up2都是栈上的元素,而vector存放元素是在堆上存的
//想要往vec里面存的话,只能传右值的unique_ptr,这样就会调用移动构造了
vec.push_back(unique_ptr<Point>(new Point(1,2)));
vec.push_back(move(up));
vec[1]->print();//段错误,因为up的所有权已经被移动构造转移到了vec[0]的手上,他已经是一个空指针了
shared_ptr的使用*
特点1:共享所有权的智能指针
可以使用引用计数记录对象的个数
特征2:可以进行复制或者赋值
进行复制和赋值并不是真正的拷贝了对象,而是将引用计数+1,具备值语义
特征3:也可以作为容器的元素
作为容器元素的时候既可以传递左值又可以传递右值,(因为可以拷贝了,也可以进行赋值了)
特征4:也具备移动语义
表明也有移动构造函数与移动赋值函数
1 | class Point{ |
1 | //作为容器 |
shared_ptr的循环引用
1 | class Child; |
可能导致资源死锁
1 | shared_ptr<Parent> parentPtr(new Parent()); |
解决问题:
希望有一个指针能指向对方,但是引用计数不会加1,所以引入了weak_ptr(弱引用的智能指针)而对应的shared_ptr是强引用智能指针
强引用,指向一定会增加引用计数,只要有一个引用存在,对象就不能释放;
弱引用并不增加对象的引用计数,但是它知道所托管的对象是否还存活。
将上述Child或者Parent中的任意一个shared_ptr换成weak_ptr就可以解决
1 | class Child; |
weak_ptr的使用
weak_ptr是弱引用的智能指针,它是shared_ptr的一个补充,使用它进行复制或者赋值时,并不会导致引用计数加1,是为了解决shared_ptr的问题而诞生的。
weak_ptr知道所托管的对象是否还存活,如果存活,必须要提升为shared_ptr才能对资源进行访问,不能直接访问。
而且weak_ptr可以用shered_ptr创建出来
1
2
3
4
5
6
7 shared_ptr<int> sp(new int(1));
weak_ptr<int>wp(sp);
cout << *sp << endl;
*wp;//error 不能直接访问所指向的资源
weak_ptr<int> wp2;
wp2 = sp;//使用shared_ptr去给weak_ptr赋值
1.可以直接使用use_count函数
如果use_count的返回值大于0,表明关联的空间还在,不过返回的是shared_ptr的use_count
2.将weak_ptr提升为shared_ptr
1
2
3 shared_ptr<int> sp(new int(10));
weak_ptr<int> wp;//无参的方式创建weak_ptr
wp = sp;//赋值这种赋值操作可以让wp也能够托管这片空间,但是它作为一个weak_ptr仍不能够去管理,甚至连访问都不允许(weak_ptr不支持直接解引用)
想要真正地去进行管理需要使用lock函数将weak_ptr提升为shared_ptr
1
2
3
4
5
6
7 shared_ptr<int> sp2 = wp.lock();
if(sp2){
cout << "提升成功" << endl;
cout << *sp2 << endl;
}else{
cout << "提升失败,托管的空间已经被销毁" << endl;
}如果托管的资源没有被销毁,就可以成功提升为shared_ptr,否则就会返回一个空的shared_ptr(空指针)
而且看似是提升,实际上是创建了一个新的shared_ptr
3.可以使用expired函数
1
2 bool expired() const noexcept;
//weak_ptr去判断托管的资源有没有被回收该函数返回true等价于use_count() == 0.
1
2
3
4
5
6 bool flag = wp.expired();
if(flag){
cout << "托管的空间已经被销毁" << endl;
}else{
cout << "托管的空间还在" << endl;
}
删除器
如果使用fopen打开文件,这时智能指针的默认处理方式就不能解决了,必须为智能指针定制删除器,也就是定制化释放资源的方式。
unique_ptr对应的删除器
1
2
3
4
5
6 void test0(){
string msg = "hello,world\n";
FILE * fp = fopen("res1.txt","a+");
fwrite(msg.c_str(),1,msg.size(),fp);
fclose(fp);
}使用unique_ptr管理
1
2
3
4
5
6 void test0(){
string msg = "hello,world\n";
unique_ptr<FILE> up(fopen("res1.txt","a+"));
fwrite(msg.c_str(),1,msg.size(),up.get());
//fclose(up.get);这一步应该自动完成
}发现数据没有被写进文件,因为unique_ptr最后默认走的是delete的路径,而delete不会刷新缓冲区,现在需求是要让unique_ptr最后走的是fclose。
定制一个struct,让其作为unique_ptr的第二个类型参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14 struct FILECloser{
void operator()(FILE * fp){
if(fp){
fclose(fp);
cout << "fclose(fp)" << endl;
}
}
}
void test(){
string msg = "hello,world\n";
unique_ptr<FILE,FILECloser> up(fopen("res1.txt","a+"));
fwrite(msg.c_str(),1,msg.size(),up.get());
}
shared_ptr的删除器
1
2
3
4
5
6 void test0(){
string msg = "hello,world\n";
sharead_ptr<FILE> up(fopen("res1.txt","a+"));
fwrite(msg.c_str(),1,msg.size(),up.get());
//fclose(up.get);这一步应该自动完成
}同Unique_ptr的问题,但是删除器的用法有所不同
用法如下,作为构造函数的第二个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 struct FILECloser{
void operator()(FILE * fp){
if(fp){
fclose(fp);
cout << "fclose(fp)" << endl;
}
}
}
void test(){
string msg = "hello,world\n";
FILECloser fc;
shared_ptr<FILE,FILECloser> up(fopen("res1.txt","a+"),fc);
fwrite(msg.c_str(),1,msg.size(),up.get());
}
智能指针的误用
智能指针被误用的情况,原因都是将一个原生裸指针交给了不同的智能指针进行托管,而造成尝试对一个对象销毁两次。
例如
1
2
3
4
5 //需要认为避免手动调用 deLete pt;
Point * pt = new Point(1,2);
unique_ptr<Point> up(pt);
unique_ptr<Point> up2(pt);
//并且这样会出double free 因为unique_ptr是独占形指针
1
2
3 unique_ptr<Point> up(new Point(1,2));
unique_ptr<Point> up2(new Point(1,2));
//这样是合理的
1
2
3
4
5 Point * pt = new Point(1,2);
shared_ptr<Point> sp(p1);
shared_ptr<Point> sp2(p1);
//仍然出现了double free 的问题
//这个sp和sp2是独立的对象,是没有关系的,注意跟复制和赋值区分,这里sp和sp2都以为自己是独享的p1对象,所以就都进行了释放,出现了double free正确的共享
1
2
3 Point * pt = new Point(1,2);
shared_ptr<Point> sp(p1);
shared_ptr<Point> sp2(sp);
—— 还有一种误用
给Point类加入了这样的成员函数
1
2
3
4
5 Point * addPoint(Point * pt){
_ix += pt->_ix;
_iy += pt->_iy;
return this;
}使用时,这样还是使得sp3和sp同时托管了同一个堆对象
1
2
3
4
5
6
7
8
9 shared_ptr<Point> sp(new Point(1,2));
shared_ptr<Point> sp2(new Point(3,4));
//创建sp3的参数实际上是sp所对应的裸指针
//效果还是多个智能指针托管了同一块空间,却没有通过shared_ptr的复制进行共同管理,变成上面第二种情况了
shared_ptr<Point> sp3(sp->addPoint(sp2.get()));
cout << "sp3 = ";
sp3->print();
//出现double free让addPoint的返回值变成shared_ptr
1
2
3
4
5 shared_ptr<Point> addPoint(Point * pt){
_ix += pt->_ix;
_iy += pt->_iy;
return shared_ptr<Point>(this);
}不过这样并没有解决问题,因为this指针仍然是sp的裸指针,创建的匿名的shared_ptr和sp共用了一个裸指针
让addPoint使用shared_from_this()这个函数返回共享*this所有权的shared_ptr
1
2
3
4
5
6
7
8
9 class Point
:public std::enable_shared_from_this<Point>
{
shared_ptr<Point> addPoint(Point * pt){
_ix += pt->_ix;
_iy += pt->_iy;
return shared_from_this();
}
};总结:智能指针的误用全都是使用了不同的智能指针托管了同一块堆空间(同一个裸指针)。