什么是智能指针
智能指针是一种运行在C++中的对象模拟指针行为的类,它提供了比原始指针更为先进和安全的内存管理模式。与传统的原始指针相比,智能指针能够自动管理其所指对象的生命周期,当智能指针的生命周期结束时,它们可以自动释放或删除其所指向的内存资源,从而减少内存泄漏的风险。
C++标准库提供了几种不同类型的智能指针,主要包括std::unique_ptr
、std::shared_ptr
和std::weak_ptr
,每种智能指针都有其特定的使用场景和目的。
智能指针的设计
在对象构造的时候获取资源,在对对象析构的时候释放资源,利用对象的生命周期来控制资源,即RAII特性
对 运算符 * 和 运算符 -> 进行重载,使智能指针也有指针一样的行为
原理及模拟实现
auto_ptr
思想
通过管理权转移的方式,保证一个资源在任何时刻都只有一个对象在对其进行管理
设计
在拷贝构造函数中,用传入对象管理的资源构造当前对象,并将原来的指针置空
在拷贝赋值函数中,先将当前资源释放,再接管传入对象的资源,并将原来的指针置空
缺点
不能对原来的ptr进行访问,否则程序崩溃
unique_ptr
思想
防止拷贝
设计
将拷贝构造和拷贝赋值私有,或加delete
缺点
无法对unique_ptr对象进行拷贝
shared_ptr
思想
引用计数,支持多个对象一起对同一个资源进行管理
设计
在构造时获取资源,并将该资源对应的引用计数设置为1
在拷贝构造中,与传入对象一起管理它管理的资源,同时将该资源的引用计数++
在拷贝赋值中,先将当前对象管理的资源对应引用计数--(如果减为0则需要释放原来的资源),然后再与传入的对象一起管理它的资源,同时将该资源对应的引用计数++
析构时,将管理资源对应的引用计数--,如果减为0则需要释放资源
线程安全相关
++和--都不是原子的,需要加锁对引用计数进行保护
定制删除器
shared_ptr不只是管理new的内存对象,也可能是new[],或是一个文件指针
因此在创建shared_ptr对象时,可以传入一个可调用对象,用该对象完成指定的资源释放
循环引用问题
什么是循环引用?如何解决? 解决原理
循环引用是指两个或多个对象之间形成了一个引用循环,从而导致它们不能被正常回收释放,这通常出现在使用智能指针进行内存管理时。特别是在使用std::shared_ptr
进行对象管理时,如果两个对象互相持有对方的std::shared_ptr
引用,那么它们的引用计数永远不会降到0,导致对象无法被自动释放,从而引起内存泄露。
假设有两个类ClassA
和ClassB
,它们互相持有对方的指针:
#include <memory>
class ClassB; // 前向声明
class ClassA {
public:
std::shared_ptr<ClassB> bPtr; // ClassA 持有 ClassB 的共享指针
~ClassA() { std::cout << "ClassA destroyed\n"; }
};
class ClassB {
public:
std::shared_ptr<ClassA> aPtr; // ClassB 持有 ClassA 的共享指针
~ClassB() { std::cout << "ClassB destroyed\n"; }
};
// 在某个函数中构造循环引用
void createCycle() {
auto a = std::make_shared<ClassA>();
auto b = std::make_shared<ClassB>();
a->bPtr = b;
b->aPtr = a;
}
在createCycle
函数中,a
和b
互相持有对方的std::shared_ptr
,形成了循环引用。由于它们的引用计数无法降到0,所以即使createCycle
函数执行完毕,a
和b
也不会被销毁,导致内存泄漏。
要解决这个问题,可以将其中一个类持有对另一个类的std::weak_ptr
。假设我们修改ClassB
使其持有ClassA
的std::weak_ptr
:
class ClassB {
public:
std::weak_ptr<ClassA> aPtr; // 将 shared_ptr 改为 weak_ptr
~ClassB() { std::cout << "ClassB destroyed\n"; }
};
这样,当createCycle
函数执行完毕,尽管ClassB
的实例仍持有ClassA
实例的引用,但这个引用是一个std::weak_ptr
,它不会增加ClassA
的引用计数。因此,当a
的引用计数降到0时,它会被正常销毁。同理,当b
的引用计数降到0时,由于b
不再增加a
的引用计数,b
也会被正常销毁。这样,就成功避免了内存泄漏的问题。
weak_ptr
思想
通过不增加/减少引用计数的方式,解决shared_ptr的循环引用问题
![](https://img-blog.csdnimg.cn/direct/8e844fdb1e924f5bb70991d532ca1085.png)
设计
- 提供一个无参的构造函数
- 支持用shared_ptr对象拷贝构造weak_ptr对象,构造时获取shared_ptr对象管理的资源
- 支持用shared_ptr对象拷贝赋值给weak_ptr对象,赋值时获取shared_ptr对象管理的资源
方式
-
监视_shared_ptr管理的资源
std::weak_ptr
设计用于监视一个资源,而该资源由一个或多个std::shared_ptr
实例“拥有”。如果所有的std::shared_ptr
都已被销毁,即引用计数归零,std::weak_ptr
将知道它所指向的资源不再存在。 -
提供对管理的资源的临时访问
通过调用
std::weak_ptr
的lock()
方法,可以尝试获取一个std::shared_ptr
,如果资源仍然存在(也就是至少有一个std::shared_ptr
仍然存在),lock()
会成功返回一个可用的std::shared_ptr
。如果资源已经被释放,lock()
会返回一个空的std::shared_ptr
。 -
不影响资源的引用计数
由于
std::weak_ptr
不增加资源的引用计数,它可以用来解决std::shared_ptr
之间可能出现的循环引用问题。当转换为std::shared_ptr
失败(例如,资源已被释放)时,它不会破坏资源的自动销毁机制。
一些情况
在智能指针管理的链表结构中,使用std::weak_ptr
生成一个std::shared_ptr
(通过lock()
方法)来临时管理节点,并不会直接影响到原有的std::weak_ptr
。std::weak_ptr
的生命周期和它是否被转换为std::shared_ptr
无关。std::weak_ptr
的存在不会增加对象的引用计数,因此,它本身并不管理对象的生命周期,而是用来观察和访问由std::shared_ptr
管理的对象。
当你通过lock()
从std::weak_ptr
得到一个std::shared_ptr
时,得到的std::shared_ptr
会增加对象的引用计数。这意味着,只要这个临时的std::shared_ptr
存在,它所指向的对象就不会被销毁。当这个std::shared_ptr
离开作用域或被显式销毁后,对象的引用计数会减少。如果引用计数变为0(意味着没有任何std::shared_ptr
指向这个对象),对象会被自动销毁。
在这个过程中,std::weak_ptr
本身的存在与否与通过它生成的std::shared_ptr
没有直接关系。std::weak_ptr
的消失也是独立于这个过程。std::weak_ptr
会在它自己的作用域结束时销毁,或者当你将它赋予新值或显式重置时。
总之:
- 当
std::weak_ptr
通过lock()
方法生成了一个std::shared_ptr
时,这个生成的std::shared_ptr
会临时增加对象的引用计数。 - 这个临时的
std::shared_ptr
存在期间,对象是安全的,不会被销毁。 - 当这个临时的
std::shared_ptr
离开作用域或被销毁后,如果它是最后一个指向对象的std::shared_ptr
,对象会被销毁。 - 原有的
std::weak_ptr
与此过程无关,它的生命周期由其作用域和程序逻辑决定。