C++智能指针的使用 shared_ptr weak_ptr unique_ptr

shared_ptr

C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己
管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的
概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常
时内存泄露等问题等,使用智能指针能更好的管理堆内存。

C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,
并且第一个已经被C++11弃用。

智能指针原理:

智能指针的思想是通过一个对象去管理另一个对象的方式,在函数退栈的时候由于栈上的内存都会被释放,释放对象的时候就会调用该对象的析构函数。 智能指针就是一个管理别的对象的类,在它析构的时候如果条件符合那么就会自动它管理的对象也进行析构调。 同时重载指针的操作运算符实现了指针的操作。

shared_ptr共享的智能指针

std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。再最后一个shared_ptr析
构的时候,内存才会被释放。
shared_ptr共享被管理对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个
shared_ptr对象销毁时,被管理对象自动销毁。
简单来说,shared_ptr实现包含了两部分

  1. 一个指向堆上创建的对象的裸指针,raw_ptr,用于指向实际的对象
  2. 一个指向内部隐藏的、共享的管理对象。share_count_object ,用于引用计数,当计数为0的时候管理的资源会被释放,同时多线程是线程安全的。

初始化

首先先说下头文件智能指针的头文件 #include

#include 
#include 
using namespace std;

class A{
public:
   A(){
       std::cout <<"A()" << endl;
   }

   ~A(){
       std::cout <<"~A()" <<endl;
   }
};

void DeleteIntPtr(int *p) {
   cout << "call DeleteIntPtr" << endl;
   delete p;
}
void shard_init()
{
 //  std::shared_ptr p1 = new int(1); //A compiler error
     std::shared_ptr<int> p2(new int(1));  //堆上开辟一个int的空间并赋值为1,交给智能指针保管
     std::shared_ptr<int> p3 = p2;		//p3和p2同时指向刚刚开辟的空间
     std::shared_ptr<int> p4 = make_shared<int>(10); //推荐的初始化方法
     std::shared_ptr<int> p5;	 //裸指针
     auto p6 = p5;
     auto p7 = make_shared<int>(1000); //这个是最推荐的写法,最简洁
   
     std::shared_ptr<int> p8(new int(10000), DeleteIntPtr); // shared_ptr 可以自定义释放内存的方法,这样一来还可以关闭文件等操作。
     std::shared_ptr<int> p9(new int(100000), [](int* p){ //使用匿名函数进行内存释放
         cout << "delete p9"<<endl;
         delete p;
     });

     std::shared_ptr<A> p10(new A[10], [](A* p){  //开辟了一个数组,这个数组中放的否是A的指针
         cout << "delete p10" <<endl;
         delete []p;
     });

     std::shared_ptr<int> p11(new int[10], [](int* p){
         cout << "delete p10" <<endl;
         delete []p;
     });

     p11.get()[10] = 100;
}

总结一下上述的内容:

  1. shared_ptr 可以进行拷贝,对个shared_ptr同时指向一块内存。
  2. shared_ptr 可以自定义内存的释放方法,如果没用没有指定那么会使用析构函数,管理资源很灵活
  3. 推荐使用 auto p = make_shared();

常用的方法

void shard_operation()
{
    auto p1 = make_shared<A>();
    //查看被引用的数量
    cout << p1.use_count() << endl;
    auto p2 = p1;
    cout << p1.use_count() << endl;
    //释放
    p1.reset();
    cout << p1.use_count() << endl;
    cout << p2.use_count() << endl;
	
	//获取裸指针,可以通过智能指针获取到裸指针,但是这是一个很危险的操作,如果不小心释放了
	//这个裸指针,那么程序会崩给你看。
    A* ptr = p1.get();
    // delete ptr;
    //获取裸指针的一般是用一些函数的参数是原声指针的情况
    // void test(A* ptr);
}
  1. use_count()可以获取该智能指针有多少引用计数。
  2. reset()可以将该指针的资源引用计数进行减1,这个接口有很多重载的函数,也可以重新设置要管理的资源
  3. get()获取资源的裸指针

要注意的问题

  1. 不要用一个原始指针初始化多个shared_ptr
int *ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr);

如果直接运行会导致两次释放,p1和p2都进行释放,从而double free

  1. 不要再函数的实参中创建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());
  1. 通过shared_from_this()返回this指针。不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针
    看如下代码:

class B
{
public:
    shared_ptr<B> GetSelf()
    {
        return shared_ptr<B>(this); // 不要这么做
    }
    ~B()
    {
        cout << "Deconstruction B" << endl;
    }
};

class C :public std::enable_shared_from_this<C>
{
public:
    shared_ptr<C> GetSelf()
    {
        return shared_from_this();
    }
    ~C()
    {
        cout << "Deconstruction B" << endl;
    }
};

void shared_ptr_waring(){
   shared_ptr<B> sp1(new B);
   //shared_ptr sp2 = sp1->GetSelf();
   cout << sp1.use_count() << endl;
   //cout << sp2.use_count() << endl;


   shared_ptr<C> spc1(new C);
   shared_ptr<C> spc2 = spc1->GetSelf();
   cout << spc1.use_count() << endl; //2
   cout << spc1.use_count() << endl; //2
}

首先在

   shared_ptr<B> sp1(new B);
   shared_ptr<B> sp2 = sp1->GetSelf();
   cout << sp1.use_count() << endl;
   cout << sp2.use_count() << endl;

如果将注释放开实际上有两个智能指针sp1和sp2指向了同一块内存,这样会导致重复释放。
如果非要返回该功能则需继承enable_shared_from_this

上述代码指针 spc1和spc2就是正常的如果打印出引用的指都是2

  1. shared_ptr 的交叉引用的问题
class D;
class E;

class D{
public:
    ~D(){
        cout << "D" <<endl;
    }

    shared_ptr<E> pe;
};

class E{
public:

    ~E(){
        cout << "E" <<endl;
    }
    shared_ptr<D> pd;
};

void circular_reference()
{
    shared_ptr<D> p1(new D);
    shared_ptr<E> p2(new E);

    p1->pe = p2;
    p2->pd = p1;

    cout << p1.use_count() <<endl; //打印2
    cout << p2.use_count() <<endl; //打印2
}

上述的内容当走出circular_reference的作用域的时候,被new 出来的D和E并没有释放。
其很好理解,函数体内的p1和p2进行引用减少了但是类中还有成员变量的引用计数,这样就会导致引用计数不会到0,从而不会释放内存。
上述的问题可以使用: ```std::weak_ptr`` 进行处理

weak_ptr(弱引用的智能指针)

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。
weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得资源的监测权,它的构造不会增加引用计数,它的析构也不会减少引用计数,纯粹只是作为一个旁观者来监视shared_ptr中管理的资源是否存在。weak_ptr还可以返回this指针和解决循环引用的问题。

说人话就是:

  1. weak_ptr 是用于协助shared_ptr解决循环引用的问题
  2. weak_ptr 只是一个监视者,它不进行资源的管理
  3. weak_ptr 可以喝shared_ptr进行转换

weak_ptr的基本用法

  1. 通过use_count()方法获取当前观察资源的引用计数
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << wp.use_count() << endl;

上述的打印结果为1, 说白了就是一个监视者

  1. expired()方法判断所观察资源是否已经释放
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
if(wp.expired())
	cout << "weak_ptr无效,资源已释放";
else
	cout << "weak_ptr有效";

打印: weak_ptr有效;

  1. 通过lock()方法可以获取到监视的shared_ptr,如果被监视的无效,那么将会返回一个空的shared_ptr。
std::weak_ptr<int> gw;
void f()
{
	if(gw.expired()) {
		cout << "gw无效,资源已释放";
	}
	else {
		auto spt = gw.lock();
		cout << "gw有效, *spt = " << *spt << endl;
	}
}
int main()
{
	{
		auto sp = atd::make_shared<int>(42);
		gw = sp;
		f();
	}
	f();
	return 0;
}

之前如果要通过shared_ptr返回this,导致释放两次的问题可以通过继承std::enable_shared_from_this的方式解决,其实在内部封装了一个weak_ptr,在返回shared_from_this();的时候通过lock创建了一个 shared_ptr。然后将spc1(上文)和spc2(上文)中的两个指针关联到了一起。要注意的是如果继承了该类,创建直接使用new创建,然后调用该方法那么会报错。

  1. weak_ptr解决循环引用问题
    在shared_ptr章节提到智能指针循环引用的问题,因为智能指针的循环引用会导致内存泄漏,可以通过weak_ptr解决该问题,只要将D或E的任意一个成员变量改为weak_ptr

class D;
class E;

class D{
public:
    ~D(){
        cout << "D" <<endl;
    }

    std::weak_ptr<E> pe;
   // shared_ptr pe;
};

class E{
public:

    ~E(){
        cout << "E" <<endl;
    }
    shared_ptr<D> pd;
};

void circular_reference()
{
    shared_ptr<D> p1(new D);
    shared_ptr<E> p2(new E);

    p1->pe = p2; //p1->pe : E.pe(weak_ptr)
    p2->pd = p1; //p2->pd : shared_ptr

    cout << p1.use_count() <<endl; 2
    cout << p2.use_count() <<endl; 1
}

打印信息:
2
1
E
D
weak_ptr 不占用引用计数, 所以p2.use_count()为1。 当析构的时候首先p2所指向的资源被析构,之后p2.pd引用计数会减少1,之后p1析构从而正确释放资源,解决循环引用的问题。

unique_ptr

  1. unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr,但是可以使用std::move();
unique_ptr<T> my_ptr(new T);
unique_ptr<T> my_other_ptr = my_ptr;
unique_ptr<T> my_other_ptr = std::move(my_ptr);

第二行会报错。
可以用第三行的方式。

  1. unique_ptr可以指向一个数组,代码如下所示
std::unique_ptr<int []> ptr(new int[10]);
ptr[9] = 9;
  1. unique_ptr再删除的时候需要确定删除器的类型,所以不能像shared_ptr那样直接指定删除器,可以这样写
std::unique_ptr<int, void(*)(int*)> ptr5(new int(1), [](int *p){delete p;});

关于shared_ptr和unique_ptr的使用场景是要根据实际应用需求来选择。如果希望只有一个智能指针管
理资源或者管理数组就用unique_ptr,如果希望多个智能指针管理同一个资源就用shared_ptr。

上面的就是目前三种智能指针的使用方法,后面在实战中用到了如果有什么要注意的后面再更新吧。
不早了 晚安晚安。

你可能感兴趣的:(c++,开发语言,后端)