C++智能指针学习笔记

C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放是由程序员完成的。使用普通指针,容易造成堆内存泄漏(忘记释放)、二次释放等问题。使用智能指针可用于动态资源管理,定义一个类来封装资源的分配和释放,在构造函数中完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。

智能指针的原理:智能指针是一个类,这个类的构造函数中传入一个普通指针,析构函数中释放传入的指针。智能指针的类是栈上的对象,智能指针指向堆上开辟的空间,函数结束时,栈上的函数会自动被释放,智能指针指向的内存也会随之消失,防止内存泄漏。智能指针的实现需要实现构造、析构、拷贝构造、操作符重载。

智能指针是RAII机制对普通指针的一层封装。智能指针的行为动作像指针,本质上确是对象。

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

1、shared_ptr共享的智能指针

std::shared_ptr使用引用计数,每个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。

shared_ptr共享被管理对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个shared_ptr对象销毁时,被管理对象自动销毁。

简单来说,shared_ptr实现包含了两部分:一个指向堆上创建的对象的裸指针,raw_ptr; 一个指向内部隐藏的、共享的管理对象,share_count_object。

1.1 shared_ptr的基本用法

1.初始化

通过构造函数、std::shared_ptr辅助函数和reset方法来初始化shared_ptr,代码如下:

// 智能指针初始化
std::shared_ptr p1(new int(1));
std::shared_ptr p2 = p1;
std::shared_ptr p3;
p3.reset(new int(1));

我们应该优先使用make_shared来构造智能指针,因为它更高效。

auto sp1 = make_shared(100);
//相当于
shared_ptr sp1(new int(100));

2.获取原始指针

当需要获取原始指针时,可以通过get方法来返回原始指针,代码如下:

std::shared_ptr ptr(new int(1));

int *p = ptr.get();

我们需要谨慎使用p.get()的返回值,并遵守以下几个约定:

  • 不要保存p.get()的返回值,无论是保存为裸指针还是shared_ptr都是错误的。
  • 保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针。
  • 不要delete p.get()的返回值,可能会导致对一块内存delete两次的错误。

3.指定删除器

如果用shared_ptr管理非new对象或没有析构函数的类时,应该为其传递合适的删除器。删除器可以是一个lambda表达式,示例代码如下:

std::shared_ptr p(new int(1),[](int *p){

    cout<< "call lambda delete p"<

    delete p;});

当我们用shared_ptr 管理动态数组时,需要指定删除器,因为shared_ptr的默认删除器是不支持数组对象,示例代码如下:

std::shared_ptr p3(new int[10], [](int *p){delete []p;});

1.2使用shared_ptr注意事项

1.不要用一个原始指针初始化多个shared_ptr。

int *ptr = new int;
shared_ptr p1(ptr);
shared_ptr p2(ptr); // 逻辑错误

2.不要在函数实参中创建shared_ptr,对于下面的写法:

function(shared_ptr(new int),g()); //有缺陷

因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也可能从左到右,所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还没有创建, 则int内存泄漏了,正确的写法应该是先创建智能指针,代码如下:

shared_ptr p(new int);
function(p, g());

3.通过shared_from_this()返回this指针。不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,这样可能会导致重复析构。正确返回this的shared_ptr的做法是:让目标类通过std::enable_shared_from_this类,然后使用基类的成员函数shared_from_this()来返回this的shared_ptr。

4.避免循环引用。循环引用会导致内存泄露。

2、unique_ptr独占的智能指针

unique_ptr持有对对象的独有权,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。

unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。

unique_ptr是一个独占型智能指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。

unique_ptr my_ptr(new T);
unique_ptr my_other_ptr = my_ptr; // 报错,不能复制

unique_ptr不允许复制,但可以通过std::move来转移到其他的unique_ptr,这样它本身就不再拥有原来指针的所有权了。例如:

unique_ptr my_ptr(new T); // 正确
unique_ptr my_other_ptr = std::move(my_ptr); // 正确
unique_ptr ptr = my_ptr; // 报错,不能复制

  • unique_ptr可以指向一个数组,示例代码如下:

std::unique_ptr ptr(new int[10]);

ptr[9]=9;

std::shared_ptr ptr2(new int[10]);//不合法

unique_ptr指定删除器和shared_ptr有区别。unique_ptr需要确定删除器的类型,所以不能像shared_ptr那样直接指定删除器,示例代码如下:

std::unique_ptr ptr5(new int(1), [](int *p){delete p;}); //正确

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

3、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指针和解决循环引用的问题。

weak_ptr的基本用法

  1. 通过use_count()方法获取当前观察资源的引用计数
  2. 通过expired()方法判断所观察资源是否已经释放
  3. 通过lock方法获取监视的shared_ptr

weak_ptr返回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返回。

需要注意的是,获取自身智能指针的函数尽在shared_ptr的构造函数被调用之后才能使用,因为
enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。

weak_ptr解决循环引用问题

shared_ptr的循环引用问题,可以通过weak_ptr解决,只需要将两个类中的任意一个成员变量改为weak_ptr。详情可以参考C++11 weak_ptr智能指针详解

4、智能指针安全问题

引用计数本身是安全的,至于智能指针是否安全需要结合实际使用分情况讨论:

情况1:多线程代码操作的是同一个shared_ptr对象,此时是不安全的。

情况2:多线程代码操作的不是同一个shared_ptr对象,此时是安全的。

参考资料

智能指针的原理及其应用_Dream的技术博客_51CTO博客

<<零声教育--C++进阶重点知识精讲1>>

你可能感兴趣的:(C++,c++,学习,开发语言)