动态内存管理引起的所谓内存泄漏的问题是编程领域的一大顽疾,它的成因非常直截了当---“只拿不还”,但它产生的缘由有时却非常隐晦需要非常仔细布局你的每一行代码才能抵抗它们对你的系统的侵蚀。而智能指针就能解决上面的问题,它的主要的功能就是自动帮我们管理内存。
1、智能指针主要解决以下问题
3、智能指针的使用
3.1 shared_ptr
3.1.1 内存模型:
3.1.1 基本用法和函数
shared_ptr的初始化
// 初始化的三种方式,优选第1 和第 2种,因为 make_shared是 构造函数,更高效auto s = make_shared < int > ( 100 );shared_ptr < int > s = make_shared < int > ( 100 );shared_ptr < int > sp1 ( new int ( 100 ));//不能将一个原始指针直接赋值给一个智能指针,例如,下面这种方法是错误的:std::shared_ptr < int > p = new int ( 1 );
shared_ptr的常用函数
auto s = make_shared < int > ( 100 );s . reset ( new int ( 200 ));
std::shared_ptr < int > ptr ( new int ( 1 ));int * p = ptr . get (); //delete p ; // 不小心释放 就会造成double free错误p.get() 的返回值就相当于一个裸指针的值,谨慎使用p.get() 的返回值
指定删除器
如果用shared_ptr管理非new对象或是没有析构函数的类时,应当为其传递合适的删除器。
#include
#include
using namespace std;
void DeleteIntPtr(int *p) {
cout << "call DeleteIntPtr" << endl;
delete p;
}
int main()
{
std::shared_ptr p(new int(1), DeleteIntPtr);
return 0;
}
std::shared_ptr < int > p ( new int ( 1 ), []( int * p ) {cout << "call lambda delete p" << endl ;delete p ;});
std::shared_ptr<int> p3(new int[10], [](int *p) { delete [] p;});
3.1.2 使用shared_ptr要注意的问题
不要用一个原始指针初始化多个shared_ptr
例如下面错误范例:
int * ptr = new int ;shared_ptr < int > p1 ( ptr );shared_ptr < int > p2 ( ptr ); // 逻辑错误
不要在函数实参中创建shared_ptr
function(shared_ptr<int>(new int), g()); //有缺陷
因为 C++ 的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的 ,一般是从右到左,但也可能从左到右,所以,可能的过程是先new int ,然后调用 g() ,如果恰好 g() 发生异常,而 shared_ptr 还没有创建, 则int 内存泄漏了.正确的写法应该是先创建智能指针,代码如下:shared_ptr < int > p ( new int );function ( p , g ());
通过shared_from_this()返回this指针
不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,因此,这样可能会导致重复析构,看下面的例子。
/*
错误范例:
由于用同一个指针(this)构造了两个智能指针sp1和sp2,而他们之间是没有任何关系
的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。
*/
#include
#include
using namespace std;
class A
{
public:
shared_ptr GetSelf()
{
return shared_ptr(this); // 不要这么做
}
~A()
{
cout << "Destructor A" << endl;
}
};
int main()
{
shared_ptr sp1(new A);
shared_ptr sp2 = sp1->GetSelf();
return 0;
}
/*
正确范例,正确返回this的shared_ptr的做法是:
让目标类通过std::enable_shared_from_this基类的方法shared_from_this()来返回一个新的
std::shared_ptr 对象来作为this的shared_ptr,如下所示。
*/
#include
#include
using namespace std;
class A: public std::enable_shared_from_this
{
public:
shared_ptrGetSelf()
{
return shared_from_this(); //正确的做法
}
~A()
{
cout << "Destructor A" << endl;
}
};
int main()
{
shared_ptr sp1(new A);
shared_ptr sp2 = sp1->GetSelf(); // ok
return 0;
}
#include
#include
using namespace std;
class A;
class B;
class A {
public:
std::shared_ptr bptr;
~A() {
cout << "A is deleted" << endl;
}
};
class B {
public:
std::shared_ptr aptr;
~B() {
cout << "B is deleted" << endl;
}
};
int main()
{
{
std::shared_ptr ap(new A);
std::shared_ptr bp(new B);
ap->bptr = bp;
bp->aptr = ap;
}
cout<< "main leave" << endl; // 循环引用导致ap bp退出了作用域都没有析构
return 0;
}
3.2.1 unique_ptr使用特点
1、unique_ptr 不允许复制,但可以通过函数返回给其他的 unique_ptr ,还可以通过 std::move 来转移到其他的unique_ptr ,这样它本身就不再拥有原来指针的所有权了。例如unique_ptr < T > my_ptr ( new T ); // 正确unique_ptr < T > my_other_ptr = std::move ( my_ptr ); // 正确unique_ptr < T > ptr = my_ptr ; // 报错,不能复制 2、 unique_ptr可以指向一个数组,而 shared_ptr不可以, 代码如下所示std::unique_ptr < int [] > ptr ( new int [ 10 ]);ptr [ 9 ] = 9 ;std::shared_ptr < int [] > ptr2 ( new int [ 10 ]); // 这个是不合法的3、 unique_ptr 指定删除器和 shared_ptr 有区别std::shared_ptr < int > ptr3 ( new int ( 1 ), []( int * p ){ delete p ;}); // 正确std::unique_ptr < int > ptr4 ( new int ( 1 ), []( int * p ){ delete p ;}); // 错误unique_ptr 需要确定删除器的类型,所以不能像 shared_ptr 那样直接指定删除器,可以这样写:std::unique_ptr < int , void ( * )( int* ) > ptr5 ( new int ( 1 ), []( int * p ){ delete p ;}); //正确
share_ptr虽然已经很好用了,但是有一点share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。
weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
3.3.1 weak_ptr的基本用法
1. 通过use_count()方法获取当前观察资源的引用计数,如下所示:
shared_ptr < int > sp ( new int ( 10 ));weak_ptr < int > wp ( sp );cout << wp . use_count () << endl ; // 结果讲输出 12. 通过 expired() 方法判断所观察资源是否已经释放,如下所示:shared_ptr < int > sp ( new int ( 10 ));weak_ptr < int > wp ( sp );if ( wp . expired ())cout << "weak_ptr 无效 , 资源已释放 " ;elsecout << "weak_ptr 有效 " ;3. 通过 lock 方法获取监视的 shared_ptr ,如下所示:std::weak_ptr < int > gw ;void f (){auto spt = gw . lock ();if ( gw . expired ()) {cout << "gw 无效 , 资源已释放 " ;}else {cout << "gw 有效 , *spt = " << * spt << endl ;}}int main (){{auto sp = std::make_shared < int > ( 42 );gw = sp ;f ();}f ();return 0 ;}
#include
#include
using namespace std;
class A: public std::enable_shared_from_this
{
public:
shared_ptrGetSelf()
{
return shared_from_this(); //
}
~A()
{
cout << "Destructor A" << endl;
}
};
int main()
{
shared_ptr sp1(new A);
shared_ptr sp2 = sp1->GetSelf(); // ok
return 0;
}
在外面创建A对象的智能指针和通过对象返回this的智能指针都是安全的,因为shared_from_this()是内部的weak_ptr调用lock()方法之后返回的智能指针,在离开作用域之后,spy的引用计数减为0,A对象会被析构,不会出现A对象被析构两次的问题。
#include
#include
using namespace std;
class A;
class B;
class A {
public:
std::shared_ptr bptr;
~A() {
cout << "A is deleted" << endl;
}
};
class B {
public:
std::shared_ptr aptr;
~B() {
cout << "B is deleted" << endl;
}
};
int main()
{
{
std::shared_ptr ap(new A);
std::shared_ptr bp(new B);
ap->bptr = bp;
bp->aptr = ap;
}
cout<< "main leave" << endl; // 循环引用导致ap bp退出了作用域都没有析构
return 0;
}
1. weak_ptr 在使用前需要检查合法性。weak_ptr < int > wp ;{shared_ptr < int > sp ( new int ( 1 )); //sp.use_count()==1wp = sp ; //wp 不会改变引用计数,所以 sp.use_count()==1shared_ptr < int > sp_ok = wp . lock (); //wp 没有重载 -> 操作符。只能这样取所指向的对象}shared_ptr < int > sp_null = wp . lock (); //sp_null .use_count()==0;因为上述代码中 sp 和 sp_ok 离开了作用域,其容纳的 K 对象已经被释放了。得到了一个容纳NULL 指针的 sp_null 对象。 在使用 wp 前需要调用 wp.expired() 函数判断一下。 因为wp 还仍旧存在,虽然引用计数等于 0 ,仍有某处 “ 全局 ” 性的存储块保存着这个计数信息。直到最后一个weak_ptr 对象被析构,这块 “ 堆 ” 存储块才能被回收。否则 weak_ptr 无法直到自己所容纳的那个指针资源的当前状态。
情况 1 :多线程代码操作的是同一个 shared_ptr 的对象,此时是不安全的。比如 std::thread 的回调函数,是一个 lambda 表达式,其中引用捕获了一个 shared_ptrstd::thread td ([ & sp1 ]()){....});又或者通过回调函数的参数传入的 shared_ptr 对象,参数类型引用void fn ( shared_ptr < A >& sp ) {...}..std::thread td ( fn , sp1 );这时候必然不是线程安全的。情况 2 :多线程代码操作的不是同一个 shared_ptr 的对象这里指的是管理的数据是同一份,而 shared_ptr 不是同一个对象。比如多线程回调的 lambda的是按值捕获的对象。std::thread td ([ sp1 ]()){....});另一个线程传递的 shared_ptr 是值传递,而非引用:void fn ( shared_ptr < A > sp ) {...}..std::thread td ( fn , sp1 );这时候每个线程内看到的 sp ,他们所管理的是同一份数据,用的是同一个引用计数。但是各自是不同的对象,当发生多线程中修改sp 指向的操作的时候,是不会出现非预期的异常行为的。也就是说,如下操作是安全的。void fn ( shared_ptr < A > sp ) {...if (..){sp = other_sp ;} else {sp = other_sp2 ;}}需要注意:所管理数据的线程安全性问题 。显而易见,所管理的对象必然不是线程安全的,必然 sp1 、sp2、 sp3 智能指针实际都是指向对象 A , 三个线程同时操作对象 A ,那对象的数据安全必然是需要对象A自己去保证。