智能指针
创建一个智能指针:
std::shared_ptr<A> a = std::make_shared<A>(); // A任意对象
make_shared<>()是一种 安全、高效的智能对象构造方式,会在堆上同时分配一个对象和一个引用计数控制器(control block),并返回一个封装了它们的 shared_ptr
,(控制块和对象是分别存储的,但 make_shared
会合并在一块内存中以优化分配)。你不用直接操作裸指针,它帮你全自动管理生命周期 。
什么是控制块(control block)?
控制块包含:
字段 | 作用 |
---|---|
strong_count | 有多少个 shared_ptr 指向这个对象 |
weak_count | 有多少个 weak_ptr 正在观察它 |
可能还有 | 自定义析构器、删除器、调试信息等 |
控制块也分配在堆上,生命周期由引用计数控制。
智能对象结构图:
循环引用是怎么发生的 ?
struct B; // 提前声明
struct A {
std::shared_ptr<B> ptr_to_b;
~A() { std::cout << "A destroyed\n"; }
};
struct B {
std::shared_ptr<A> ptr_to_a;
~B() { std::cout << "B destroyed\n"; }
};
void demo() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->ptr_to_b = b;
b->ptr_to_a = a;
} // a 和 b 永远不会被销毁(内存泄漏)
逐步拆分代码
- 对象声明
struct B; // 提前声明
struct A {
std::shared_ptr<B> ptr_to_b; // 指向结构体B的智能指针
~A() { std::cout << "A destroyed\n"; }
};
struct B {
std::shared_ptr<A> ptr_to_a; // 指向结构体A的智能指针
~B() { std::cout << "B destroyed\n"; }
};
- 创建智能对象
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
创建智能对象会在堆上同时分配一个对象和一个引用计数控制器(control block),返回对应的智能指针。
- 循环引用
a->ptr_to_b = b;
b->ptr_to_a = a;
如图所示,两个智能对象相互引用,导致strong_count始终为1,相互锁死,都无法释放,造成内存泄漏。
注:图中将 shared_ptr
简化为直接指向目标对象,实际在底层它持有的是一份指向共享控制块(control block)的引用,并通过该控制块间接管理对象指针和引用计数。本图为了突出“循环引用”的结构逻辑,采用了直连对象的方式表示。
什么是strong_ptr、weak_ptr ?
shared_ptr
:强引用(Strong Pointer)
是资源的“拥有者”,只要有一个
shared_ptr
存在,对象就不会被释放。
-
拥有对资源的独立控制权
-
每创建一个
shared_ptr
,控制块中的strong_count
+1 -
只有当所有
shared_ptr
被销毁时,资源才会被真正释放 -
最常用的智能指针类型,负责延长资源生命周期
std::shared_ptr<MyObj> sp = std::make_shared<MyObj>();
weak_ptr
:弱引用(Weak Pointer)
是资源的“观察者”,不拥有资源,也不影响其生命周期。
-
不增加
strong_count
,只影响控制块的weak_count
-
不能直接访问对象,必须先
.lock()
转成shared_ptr
-
常用于打破循环引用,防止内存泄漏
-
适合缓存、观察、非拥有关系的场景
std::weak_ptr<MyObj> wp = sp; // 观察 sp,但不控制它
auto maybe = wp.lock(); // 转为 shared_ptr(如果还活着)
if (maybe) { maybe->doSomething(); } // 安全使用
利用weak_ptr避免循环引用
struct B;
struct A {
std::shared_ptr<B> ptr_to_b;
~A() { std::cout << "A destroyed\n"; }
};
struct B {
std::weak_ptr<A> ptr_to_a; // 用 weak_ptr 防止循环引用
~B() { std::cout << "B destroyed\n"; }
};
void demo() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->ptr_to_b = b;
b->ptr_to_a = a;
} // A 和 B 都会被正常销毁
逐步拆分代码
struct B;
struct A {
std::shared_ptr<B> ptr_to_b;
~A() { std::cout << "A destroyed\n"; }
};
---
auto a = std::make_shared<A>();
和前面相同,构造了一个指向B的智能对象:
但是,在B的结构中作出调制,改为weak_ptr。
struct B {
std::weak_ptr<A> ptr_to_a; // 用 weak_ptr 防止循环引用
~B() { std::cout << "B destroyed\n"; }
};
---
auto b = std::make_shared<B>();
a->ptr_to_b = b;
b->ptr_to_a = a;
std::weak_ptr
是一种不拥有资源的智能指针。它不直接指向目标对象本身,而是通过共享的控制块(control block)来观察该对象的状态,从而判断对象是否仍然存在。
将weak_ptr转化为shared_ptr会发生什么?
auto new_shared_ptr = b->ptr_to_a.lock();
weak_ptr.lock()
会通过共享的控制块(control block)创建一个新的 shared_ptr 实例,该实例与其他 shared_ptr 共同管理同一个对象,引用计数(strong_count)增加 1。
但不同于前面的循环引用,由于 weak_ptr
自身不持有资源,这不会引入循环引用。可以通过释放new_shared_ptr进而销毁(如果没有其他的shared_ptr)对象A,从而避免内存泄漏。