c++11智能指针
智能指针是存储指向动态分配(堆)对象指针的类,用于生存期控制、能够确保在离开指针所在作用域时,自动正确的销毁动态分配的对象,防止内存泄露。
c++11提供了3种智能指针:std::shared_ptr, std::unique_ptr, std::weak_ptr,使用时需要引用头文件< memory>。
1. shared_ptr智能指针
shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。
初始化
可以通过构造函数、std::make_shared< T>辅助函数和reset方法来初始化shared_ptr:
//智能指针的初始化 std::shared_ptr<int> p = make_shared<int>(42); std::shared_ptr<int> p(new int(1)); std::shared_ptr<int> p2 = p; std::shared_ptr<int> ptr; ptr.reset(new int(1)); if(ptr){ cout << "ptr is not null" << endl; } //shared_ptr 不能通过原始指针赋值来初始化 std::shared_ptr<int> p3 = new int(10); //错误
对于一个未初始化的智能指针,可以通过reset方法来初始化,当智能指针中有值的时候,调用reset会使引用计数减 1.另外,智能指针可以通过重载的bool类型操作符来判断是否为空(未初始化)。
获取原始指针
当需要获取原始指针时,可以通过get方法来返回原始指针,如:
std::shared_ptr<int> ptr(new int(10)); int*p = ptr.get();
shared_ptr重载了->和*操作符,可以像操作原始指针一样对 shared_ptr使用-> 和 *。
struct A{ ~A(){ cout << "A is deleted!" << endl; } void test(){ cout << "this is test.." << endl; } }; void TestPtr(){ std::shared_ptr<A> pa(new A); pa->test(); (*pa).test(); }
指定删除器
在智能指针引用计数降为0的时候,会释放原始指针的内存,此时在delete 原始指针的时候,可以使用自定义的析构函数。如:
void DEleteIntPtr(int* p){ delete p; } std::shard_ptr<int> p(new int, DeleteIntPtr); //或者可以写成 std::shared_ptr<int> p(new int, [](int*p) { delete p;});
当用shared_ptr管理动态数组时,需要指定删除器,因为std::shared_ptr的默认删除器不支持数组对象。
std::shard_ptr p(new int[10], [](int* p){ delete[] p;});
可以封装 make_shared_array 方法来让 shared_ptr 支持数组
template<typename T> shared_ptr<T> make_shared_array(size_t size){ return shared_ptr<T>(new T[size], default_delete<T[]>()); }
使用shared_ptr需要注意的事情
(1)不要用一个原始指针初始化多个shared_ptr,如
int* p = new int; shared_ptr<int> p1(p); shared_ptr<int> p2(p); //logic error //这样考虑,如果用同一个原始指针分别初始化了多个shared_ptr,这些shared_ptr之间并不知道相互的存在,则会在引用减为0的时候,分别析构原始指针,造成多次析构
(2)不要在函数实参中创建shared_ptr。
function (shared_ptr<int> (new int), g()); //这样做有缺陷 因为c++的函数参数的计算顺序在不同的编译器不同的调用约定下可能是不一样的,一般是从右到左,但也有从左到右,所以可能的调用过程为先 new int,然后调用g,如果恰好在调用g的过程中发生异常,而shared_ptr<int> 还没有创建,则int内存泄露了,正确的写法应该是先创建智能指针,代码如下: shared_ptr<int> p(new int); f(p, g);
(3)通过shared_from_this() 返回this指针,不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,因此这样可能会导致重复析构。如
struct A{ shared_ptr<A> GetSelf(){ return shared_ptr<A>(this); //这样做有问题 } }; int main(){ shared_ptr<A> sp1(new A); shared_ptr<A> sp2 = sp1->GetSelf(); return 0; } //用一个指针(this)构造了两个智能指针 sp1和 sp2,而它们之间是没有任何关系的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。
正确返回this的shared_ptr的做法是:让目标类通过派生 std::enable_shared_from_this类,然后使用基类的成员函数 shared_from_this来返回this的shared_ptr
class A:public std::enable_shared_from_this<A>{ std::shared_ptr<A> GetSelf(){ return shared_from_this(); } }; .... std::shared_ptr<A> spy(new A); std::shared_ptr<A> p = spy->GetSelf();
(4)要避免循环引用。智能指针最大的一个陷阱是循环引用,循环引用会导致内存泄露。如下为一个典型的循环引用场景
struct A; struct B; struct A{ std::shared_ptr<B> bptr; ~A(){ cout << "A is deleted!" << endl; } }; struct B{ std::shared_ptr<A> aptr; ~B(){ cout << "B is deleted!" << endl; } }; int Test(){ { std::shared_ptr<A> ap(new A); std::shared_ptr<B> bp(new B); ap->bptr = bp; bp->aptr = ap; } return 0; } //结果是ap和bp都不会被删除,存在内存泄露。循环引用导致ap和bp的引用计数都为2,在离开作用域之后,ap和bp的引用计数减1,并不会减为0,导致两个智能指针都不会被析构。 解决的方法是把A和B任何一个成员变量改为 weak_ptr。
2. unique_ptr智能指针
unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享器内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr,但可以通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,这样他本身就不拥有原来指针的所有权。
unique_ptr<T> myPtr(new T); unique_ptr<T> otherPtr = myPtr;//错误,不能复制unique_ptr unique_ptr<T> myOtherPtr = std::move(myPtr); //ok
unique_ptr 还可以指向一个数组,如
std::unique_ptr<int []> ptr(new int[10]); //ok std::shared_ptr<int []> ptr(new int[10]); //就是错误的,shared_ptr不支持T[],但可以将 T* 指向一个T的数组
unique_ptr重载了->和*操作符,可以像操作原始指针一样对unique_ptr进行操作。
unique_ptr指定删除器时候要明确指定删除器的类型,如
std::unique_ptr<int> myPtr(new int, [](int* p){delete p;}); //错误的,因为没有在模板参数中明确的指定删除器的类型 std::unique_ptr<int, void(*)(int*)> myPtr(new int, [](int*p){ delete p;});//ok /*不过这种做法在lambda没有捕获变量的情况下是正确的,如果捕获了变量,则会编译报错, 因为lambda在没有捕获变量的情况下可以直接转换为函数指针,而如果捕获了变量,则无法 直接转换为函数指针。(可以想象捕获了变量之后,就需要在lambda的闭包中增加这些信息, 而一个函数指针,就4(8)字节,显然无法存储这些信息) */ //如果希望unique_ptr 的删除器支持lambda,可以这样写: std::unique_ptr<int, std::function<void(int*)>> ptr(new int(1), [&](int *p){ delete p;});
还可以自定义unique_ptr的删除器,如下:
#include<memory> #include<functional> using namespace std; struct MyDeleter{ void operator() (int* p){ cout << "delete" << endl; delete p; } }; int main(){ std::unique_ptr<int, MyDeleter> p(new int(1)); return 0; }
3. weak_ptr智能指针
弱引用指针weak_ptr是用来监视shared_ptr的,不会使引用计数加1,它不会管理shared_ptr内部的指针,主要是为了监视shared_ptr的生命周期。
weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得资源的监视权。
基本用法
(1)通过use_count()方法来获得当前观测资源的引用计数,代码如下:
shared_ptr<int> sp(new int(10)); weak_ptr<int> wp(sp); cout << wp.use_count() << endl; //结果将输出1
(2)通过expired()方法来判断所观测的资源是否已经释放。代码如下:
shared_ptr<int> sp(new int(10)); weak_ptr<int> wp(sp); if(wp.expired()){ cout << "weak ptr 无效,所监视的智能指针已经被释放!" << endl; }
(3)通过lock方法来获取所监视的shared_ptr,如:
std::weak_ptr<int> gw; void f(){ if (gw.expired()){ cout << "gw is expired!" << endl; }else{ auto sp = gw.lock(); //获得监视的shared_ptr cout << "get data " << *sp << endl; } }
weak_ptr与enable_shared_from_this
不能直接将this指针返回为 shared_ptr,需要通过派生类 std::enable_shared_from_this 类,并通过其方法 shared_from_this 来返回智能指针,原因是std::enable_shared_from_this类中有一个weak_ptr。这个weak_ptr用来观测this智能指针,调用shared_from_this()方法时,会调用内部这个weak_ptr的lock()方法,将所观测的shared_ptr返回。
weak_ptr解决循环引用
使用shared_ptr智能指针要注意到循环引用的问题,可以利用weak_ptr来消除循环引用。
struct A; struct B; struct A{ std::weak_ptr<B> bptr; //将原来的shared_ptr改为 weak_ptr ~A(){ cout << "A is deleted!" << endl; } }; struct B{ std::shared_ptr<A> aptr; ~B(){ cout << "B is deleted!" << endl; } }; int Test(){ { std::shared_ptr<A> ap(new A); std::shared_ptr<B> bp(new B); ap->bptr = bp; bp->aptr = ap; } return 0; } //ap引用计数为2,bp引用计数为1,在离开ap和bp的作用域之后bp析构,引用计数为0,则析构bp指向的原始指针;离开作用域ap析构,引用计数为1,由于bp的析构导致bp->aptr无效,从而ap的引用计数减为0,接着ap指向的原始指针被释放。
4. 通过智能指针管理第三方库分配的内存
第三方库分配的内存一般需要通过第三方库提供的释放接口才能释放,由于第三方库返回的指针一般都是原始指针,在用完之后如果没有调用释放接口,很容易造成内存泄露。
通过智能指针管理第三方库分配的内存,就可以不用考虑内存泄露的问题。
void*p = GetHandle()->Create(); //第三方库分配的内存 std::shared_ptr<void> sp(p, [this](void*p){GetHandle()->Release(p);}); //改进,写成一个函数 std::shared_ptr<void> Guard(void* p){ return std::shared_ptr<void> sp(p, [this](void* p){GetHandle()->Release(p);}); } void*p = GetHandle()->Create(); auto sp = Guard(p); .... //上述的Guard(p)可能写成这样 void*p = GetHandle()->Create(); Guard(p); //这样,返回一个临时的shared_ptr, 该句结束之后就析构,同时原始的p也被释放。 //为了避免上述的问题,将Guard写成一个宏 #define GUARD(p) std::shared_ptr<void> p ##p(p, [](void*p){GetHandle()->Release(p);});