std::auto_ptr
基本用法
1 | //第一种形式 |
智能指针对象ap1和ap2均持有一个在堆上分配int对象,这两块堆内存在对象释放时得以释放。
std::auto_ptr 经常误用的是复制操作,当复制auto_ptr对象时(拷贝复制或operator=复制),原对象的堆内存会转移到复制的对象上,原指针就空了。
1 | std::auto_ptr<int> ap1(new int(1)); |
ap1的堆内存转移到了ap2,ap3的堆内存转移到了ap4
因此在使用中,应尽量避免使用std::auto_ptr(C++11之后 该指针已被弃用,不应该再使用它)
std::unique_ptr
std::unique_ptr对指向的堆内存具有唯一控制权,引用计数永远为1,对象销毁时释放对应的堆内存。
基本使用:
1 | //第一种形式 |
注
std::unique_ptr禁止复制操作
std::unique_ptr类的拷贝构造函数和赋值operator=函数 都被标记为=delete
禁止复制操作存在特例,即可以通过一个函数返回一个std::unique_ptr:
1 | std::unique_ptr<int> func(int val) { |
实现方法:使用移动构造,即std::move
1 | std::unique_ptr<int> up5(new int(2)); |
std::move将up5持有的堆内存转移给了up6,最后up5不再持有堆内存,即是一个空的智能指针对象。
std::move操作是否有意义,取决于是否实现了移动构造(Move Constructor)和移动赋值(operator=)运算符,而unique_ptr正好实现了这两个函数。
1 | template<typename T, typename Deletor> |
std::unique_ptr不仅可以持有一个堆对象,也可以持有一组堆对象,
1 | std::unique_ptr<int[]> up7(new int[10]); |
std::shared_ptr和std::weak_ptr也可以持有一组堆对象,用法与std::unique_ptr一致。
自定义 智能指针对象持有的资源的释放函数
默认情况下,智能指针对象在析构时只会释放其持有的堆内存,调用delete或delete[],但是有些时候,不止要回收指针对象,还需要回收与该指针相关的其他内存,这时,就需要自定义智能指针的资源释放函数了。
1 |
|
自定义std::unique_ptr的资源释放函数规则是:
1 | std::unique_ptr<T,DeletorFuncPtr> |
其中T是要释放的对象类型,DeletorFuncPtr是自定义函数指针,这里可以使用decltype(releaseFunc)让编译器自行推导releaseFunc的类型
1 | std::unique_ptr<Student, void(*)(Student* ps)> pStudent(new Student(), releaseFunc); |
std::shared_ptr
std::unique_ptr对持有的资源具有独占性,而std::shared_ptr对持有的资源具有共享性,即多个shared_ptr可以同时持有同一个资源对象,每增加一个shared_ptr指向该对象,该对象的引用计数就+1,每析构一个,引用计数-1,当引用计数为0时,该对象被释放回收,多个线程之间,递增或者递减引用计数是安全的,但并不意味着,多个线程之间同时操作引用对象时安全的。std::shared_ptr提供一个use_count()方法来获取当前持有资源的引用计数。除此之外,其余的用法与std::unique_ptr基本相同。
1 | //初始化1 |
优先使用std::make_shared去初始化std::shared_ptr对象。
std::enable_shared_from_this
在开发中,有时候需要在类中返回包裹当前对象(this)的std::shared_ptr对象给外部使用,有此需求的类只要继承自己的std::enable_shared_from_this
1 | class A : public std::enable_shared_from_this<A> { |
getSelf方法返回自身的std::shared_ptr对象,方法中直接返回shared_from_this()。
陷阱一:不应该共享栈对象的this给智能指针对象
1 | //与上文代码相同 |
崩溃原因:智能指针是为了管理堆对象的,而a是栈对象,栈对象会在函数调用结束时自行销毁,不能通过shared_from_this交给智能指针管理。
智能指针最初的设计目的就是为了管理不会自动回收的堆内存的。
陷阱二:避免std::enable_shared_from_this的循环引用问题
1 | class B : public std::enable_shared_from_this<B> { |
根据输出可以发现对象B没有被回收,发生了内存泄漏,那么这是为什么呢?
sp6创建的时候 对象引用计数为1,之后调用func函数把自身的shared_ptr赋值给了类变量m_mySelf,此时引用计数为2,之后sp6对象被销毁,此时B的引用计数为1,不会被销毁,这时就出现了矛盾,B想要销毁就要引用计数减为0,就需要销毁m_mySelf,那么就必须要销毁B对象,这样B对象就无法销毁了。因此我们实际开发中应该避免出现这样的逻辑。
std::weak_ptr
std::weak_ptr是一个不控制资源生命周期的智能指针,是对对象的一种弱引用,只是提供了对其管理对象的一个访问手段,引入的目的是为了辅助shared_ptr工作。
std::weak_ptr可以从一个std::shared_ptr或另一个std::weak_ptr对象构造,std::shared_ptr可以直接复制给std::weak_ptr,weak_ptr可以通过lock()方法获取shared_ptr,它的构造和析构不会引起引用计数的变化,weak_ptr可用于解决shared_ptr引用计数循环的问题。
1 | std::shared_ptr<int> sp1 = std::make_shared<int>(1); |
weak_ptr不管对象的生命周期,当对象被销毁时,通过expired()方法来检测,返回true,表示对象已经不存在了;false表示对象还存在,此时可以通过lock()方法获取shared_ptr继续操作对象。
1 | //tmpPoint ---->std::weak_ptr<Point> |
weak_ptr没有重写operator->和operator*方法,所以不能直接操作对象,也没有重写operator!操作,所以也不能判断引用的资源是否存在。
weak_ptr不增加引用资源的引用计数来管理资源的生命周期,是因为,即使它实现了以上几个方法,调用他们是不安全的,因为在调用期间,引用的资源可能已经被销毁了。
weak_ptr的正确使用场景是那些资源如果可用就使用,不可用就不用的场景,它不参与资源的生命周期管理。
例如:在网络分层中,Session对象:利用Connection对象提供的服务工作,但是Session对象不管理Connection对象的生命周期,因为网络底层会有各种原因导致Connection对象销毁,如果Session强行持有Connection对象,会与事实矛盾。
经典示例:(订阅者模式 或者 观察者模式)
1 | class Subscriber{}; |
智能指针对象的大小
一个std::unique_ptr对象大小与一个裸指针大小相同,而std::shared_ptr对象的大小是std::unique_ptr大小的一倍。
1 | std::shared_ptr<int> sp0; |
智能指针使用注意事项
- 一旦一个对象使用智能指针管理后,就不该再使用原始裸指针去操作。
- 分清楚场合应该使用哪种类型的智能指针。
- 认真考虑,避免操作某个引用资源已经释放的智能指针。
- 作为类成员变量时,应该优先使用前置声明(forward declarations)。