原理
智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源
导引:
智能指针最初只有auto_ptr,c++98版本的。但是auto_ptr存在一个致命问题是,它支持拷贝语义嘛。所以就会造成对一个指针delete两次的问题,不安全。
而解决对一个指针多次delete的问题,一共有三种解决思路
- 加一个所有权,只有一个指针具有该内存的所有权,其他指针就算拷贝了,也不能进行析构 ——》unique_ptr应运而生
- 加一个引用计数, 一个指针引用,count就++,拷贝也会count++,赋值的话,左值的count--,右值的count++——》shared_ptr应运而生
- 定义赋值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案
- 同时因为shared_ptr具有循环引用的问题,所以加一个智能指针weak_ptr作为shared_ptr的辅助指针来解决循环引用的问题。
常用的智能指针
(1) shared_ptr
实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。
- 1、智能指针将一个计数器与类指向的对象相关联,引用计数器跟踪共有多少个类对象共享同一指针每次创建类的新对象时,初始化指针并将引用计数置为1。
- 2、当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数。
- 3、对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数。
- 4、调用析构函数时,减少引用计数(如果引用计数减至0,则删除基础对象)
(2) unique_ptr
unique_ptr采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源。转移一个unique_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空;所以unique_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中;局部变量的返回值除外(因为编译器知道要返回的对象将要被销毁);如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源,造成在结束时对同一内存指针多次释放而导致程序崩溃。
在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过 reset 方法重新指定、通过 release 方法释放所有权、通过移动语义转移所有权,unique_ptr 还可能没有对象,这种情况被称为 empty。
unique_ptr 可放在容器中,弥补了 auto_ptr 不能作为容器元素的缺点。只要对容器元素不使用拷贝操作的算法即可(如 sort())
unique_ptr 比 auto_ptr 更加安全,因为 auto_ptr 有拷贝语义,拷贝后原对象变得无效,再次访问原对象时会导致程序崩溃;unique_ptr 则禁止了拷贝语义,但提供了移动语义,即可以使用 std::move() 进行控制权限的转移
(3) weak_ptr
weak_ptr:弱引用。 引用计数有一个问题就是互相引用形成环(环形引用),这样两个指针指向的内存都无法释放。需要使用weak_ptr打破环形引用。weak_ptr是一个弱引用,它是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针。
lock函数 检查 weak_ptr 所指的对象是否仍存在,如果存在,返回一个指向共享对象的 shared_ptr,不存在则返回
w.lock(); //如果expired()为true,返回一个空shared_ptr,否则返回非空shared_ptr。
(4) auto_ptr
auto_ptr 是标准库的较早版本包含的一个类,它具有 unique_ptr 的部分特性。相比于unique_ptr,不能在容器中保存 auto_ptr,也不能从函数返回 auto_ptr。尽量使用unique_ptr
主要是为了解决“有异常抛出时发生内存泄漏”的问题 。因为发生异常而无法正常释放内存。
auto_ptr有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题;而unique_ptr则无拷贝语义,但提供了移动语义,这样的错误不再可能发生,因为很明显必须使用std::move()进行转移。
auto_ptr不支持拷贝和赋值操作,不能用在STL标准容器中。STL容器中的元素经常要支持拷贝、赋值操作,在这过程中auto_ptr会传递所有权,所以不能在STL中使用。