1 智能指针的引入
1.在动态内存管理中,如果new上一块空间,但是没有delete,就会产生内存泄露的问题。
2.但是有时候,我们new了,也delete了,但是还会出现问题。例如在new和delete之间调用了某个抛异常的函数,就有可能导致没有执行delete。
例如:fun2里面使用了new[],最后也使用了delete[],看着没有问题,但是在new和delete之间调用了fun1,而fun1里面抛了异常,但是在fun2的delete之前并没有捕获,就会导致delete没有执行,仍然会有内存泄露的问题。
void fun1()
{
throw int(11);
}
void fun2()
{
int* p = new int[10000];
fun1();
delete[] p;
}
int main()
{
try
{
fun2();
}
catch (int& e)
{
cout << "捕获" << endl;
}
system("pause");
return 0;
}
如果想要解决上面的问题,有一种方法:就是如果发现delete之前发现某个函数抛出了异常,就在delete之前捕获这个异常,并且在catch语句里面进行资源的释放,并且可以再将这个异常重新抛出。
void fun2()
{
int* p = new int[10000];
try
{
fun1();
}
catch(int& e)
{
delete[] p;
cout << "重新抛出" << endl;
throw;
}
delete[] p;
}
但是这种方法写着比较繁琐,而且如果代码很多,就有可能忘记哪个函数抛出了异常,会防不胜防。
3.因此,只要有一种方法,能够在出了作用域之后能够自动释放掉申请的空间,就会让空间得到正确的释放。而一个对象出了作用域会自动调用自己的析构函数,只要在析构函数里能够释放自己开辟的空间,就能达到目的。
4.智能指针就有上面的作用,能够自动的管理指针所指向的动态资源的释放。它不仅有着RAII的思想还能够像指针一样。(RAII:分配资源即初始化,即构造函数分配资源和初始化资源,在析构函数清理资源。像指针一样:能够解引用)。
5.智能指针实质上就是一个模板类,成员变量包含一个任意类型的指针,构造函数获得资源并初始化,析构函数清理资源。
注意:智能指针只能管理动态开辟的空间。
2 智能指针的发展史
智能指针都具有RAII的思想,即构造函数获得资源,析构函数清理资源,但是当用一个智能指针拷贝构造另一个智能指针的时候,有可能会有浅拷贝的问题,这个空间会被释放多次,智能指针的发展就是围绕着指针拷贝问题而走。
2.1 auto_ptr
(1)C++98里面有一个智能指针auto_ptr,对于拷贝构造和赋值运算符重载,该智能指针采用管理权转移的方式(当一个指针拷贝构造另一个指针时,当前指针就将对空间的管理权交给拷贝的那个指针,当前指针就指向空);
(2)但是这种方式不符合指针的要求(可以允许多个指针指向同一块空间,将一个指针赋值给另一个指针的时候,就是需要让两个指针指向同一块空间,而auto_ptr只允许一块空间上只能有一个指针指向它),并且当管理权转移之后要想再访问之前的指针,就会出错,因为之前的指针已经为NULL,就会出现解引用空指针的问题。
2.2 scoped_ptr/shared_ptr
因为auto_ptr有缺陷,但是C++标准里面从C++98到C++11之间没有出现新的智能指针能解决这个缺陷,所以在这段时间内,boost这个官方组织就增加了智能指针(scoped_ptr,shared_ptr,weak_ptr等)
(1)scoped_ptr采用防拷贝的方式(防拷贝就是不允许拷贝,拷贝就会出错;防拷贝的实现:将拷贝构造和的赋值运算符重载只声明不实现,并且声明为私有);
(2)shared_ptr为共享指针,里面采用引用计数,当有shared_ptr指向同一块空间的时候就增加引用计数,当引用计数减为0的时候才释放该智能指针管理的那块空间。
(3)但是shared_ptr有一个缺点,就是会出现循环引用的问题(当一个shared_ptr(如sp1)管理的空间里面包含一个shared_ptr的指针(_next),另一个shared_ptr(如sp2)管理的空间里面也包含一个shared_ptr指针(_prev)时,当sp1->_next = sp2;sp2->_prev = sp1;此时就会使得sp1和sp2的引用计数都变为2,当出了这个作用域sp1和sp2的引用计数都会减为1,但是只有引用计数为0时才会释放管理的空间,就会使得sp1和sp2管理的空间没有释放。
(4)所以利用weak_ptr来解决循环引用的问题,weak_ptr叫弱指针,它主要是为了配合shared_ptr使用,用来解决循环