【c++】智能指针
底层核心思想:三大智能指针均基于RAII 机制,在析构时自动释放资源,避免手动delete;差异的核心是所有权管理方式。unique_ptr 底层:独占所有权,栈上封装对象指针 + 删除器,无额外开销,禁用拷贝、支持移动,析构直接释放对象。shared_ptr 底层:共享所有权,依赖堆上控制块存储引用计数,拷贝递增强引用、析构递减强引用,最后一个释放对象,存在循环引用和额外开销问题。weak_pt
前置基础:智能指针的核心使命
C++ 三大智能指针(std::unique_ptr、std::shared_ptr、std::weak_ptr,均位于 <memory> 头文件)。C++ 没有自动垃圾回收(GC),原生指针(raw pointer)的最大问题是手动管理内存容易导致泄漏、重复释放、悬垂指针。智能指针的核心使命是:封装原生指针,利用「RAII(资源获取即初始化)」机制,在对象析构时自动释放所管理的内存,无需手动调用 delete。
三大智能指针的底层都基于 RAII,但所有权管理方式不同,这也决定了它们的底层结构和性能差异。
一、std::unique_ptr:独占式智能指针(最简单、最高效)
1. 核心设计思想
unique_ptr 实现独占所有权:一个 unique_ptr 独占它所管理的原生指针,不允许其他智能指针共享该指针,也不允许拷贝(C++11 后支持移动,转移所有权)。正因为独占,它的底层结构最简单,无额外开销(与原生指针几乎等价)。
2. 底层核心数据结构(简化版)
unique_ptr 是一个模板类,底层仅封装两个核心成员(无额外堆内存开销,属于「栈上智能指针」):
template <typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
private:
T* ptr_; // 管理的原生指针(指向堆内存对象/数组)
Deleter del_; // 删除器(默认 std::default_delete,负责释放内存)
// 关键:禁用拷贝构造和拷贝赋值(保证独占性)
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
public:
// 移动构造和移动赋值(允许转移所有权)
unique_ptr(unique_ptr&& other) noexcept;
unique_ptr& operator=(unique_ptr&& other) noexcept;
// 其他核心接口...
};
关键细节拆解:
- 独占性的实现:通过显式删除拷贝构造和拷贝赋值运算符(
= delete),禁止将一个unique_ptr拷贝给另一个,从语法上保证 “独占”。 - 删除器(Deleter):
- 默认删除器
std::default_delete<T>:底层调用delete ptr_(针对单个对象)或delete[] ptr_(针对数组,unique_ptr<T[]>特化版本)。 - 自定义删除器:支持传入函数指针、函数对象、lambda 表达式,用于特殊场景(如释放共享内存、文件句柄)。
- 删除器是模板参数,而非成员变量(C++11 优化):若使用默认删除器,
del_不会占用额外内存(空基类优化,EBO),保证unique_ptr的大小与原生指针一致。
- 默认删除器
- 无额外堆内存:所有数据(
ptr_、del_)都存储在栈上,析构时直接释放堆内存,无额外开销,是三大智能指针中性能最优的。
3. 核心接口底层实现
(1)构造函数
// 1. 默认构造:管理空指针
constexpr unique_ptr() noexcept : ptr_(nullptr), del_() {}
// 2. 原生指针构造(explicit 禁止隐式转换,避免意外)
explicit unique_ptr(T* ptr) noexcept : ptr_(ptr), del_() {}
// 3. 移动构造(转移所有权,other 变为空)
unique_ptr(unique_ptr&& other) noexcept
: ptr_(other.ptr_), del_(std::move(other.del_)) {
other.ptr_ = nullptr; // 关键:将源对象的 ptr_ 置空,避免重复释放
}
(2)析构函数(核心:自动释放内存)
~unique_ptr() noexcept {
if (ptr_ != nullptr) {
del_(ptr_); // 调用删除器释放内存,默认是 delete ptr_
}
}
- 析构时仅判断
ptr_非空,然后调用删除器,无其他额外操作,效率极高。 - 若管理的是数组(
unique_ptr<T[]>),默认删除器会调用delete[] ptr_,特化实现如下:template <typename T, typename Deleter> class unique_ptr<T[], Deleter> { private: T* ptr_; Deleter del_; public: ~unique_ptr() noexcept { if (ptr_ != nullptr) { del_(ptr_); // 默认 delete[] ptr_ } } };
(3)移动赋值运算符
unique_ptr& operator=(unique_ptr&& other) noexcept {
if (this != &other) { // 避免自赋值
// 1. 先释放当前对象管理的内存(防止内存泄漏)
if (ptr_ != nullptr) {
del_(ptr_);
}
// 2. 转移 other 的所有权
ptr_ = other.ptr_;
del_ = std::move(other.del_);
// 3. other 置空
other.ptr_ = nullptr;
}
return *this;
}
(4)释放所有权(release ())
T* release() noexcept {
T* temp = ptr_;
ptr_ = nullptr; // 释放所有权,不释放内存
return temp; // 返回原生指针,由调用者手动管理
}
- 注意:
release()仅转移原生指针的所有权,不释放内存,与析构函数的核心区别。
(5)重置(reset ())
void reset(T* new_ptr = nullptr) noexcept {
T* old_ptr = ptr_;
ptr_ = new_ptr;
if (old_ptr != nullptr) {
del_(old_ptr); // 释放旧内存,管理新内存
}
}
4. 底层内存管理流程(示例)
{
unique_ptr<int> up(new int(10)); // 构造:ptr_ 指向堆内存的 10
unique_ptr<int> up2 = std::move(up); // 移动构造:up2 接管所有权,up.ptr_ = nullptr
up2.reset(); // 重置:调用 del_(ptr_),释放堆内存,up2.ptr_ = nullptr
} // 离开作用域,up2 析构:ptr_ 已为空,无操作;up 析构:ptr_ 为空,无操作
- 全程无手动
delete,内存自动释放,且无额外开销。
5. 底层坑点与细节
- 禁止管理栈内存:
unique_ptr的删除器默认调用delete,若管理栈内存(unique_ptr<int> up(&a)),析构时会尝试释放栈内存,导致未定义行为。 - 不支持数组的下标访问(非特化版本):
unique_ptr<int> up(new int[5])是错误的,析构时会调用delete而非delete[],应使用unique_ptr<int[]> up(new int[5]),特化版本支持[]下标访问。 - 移动后源对象为空:移动后的
unique_ptr其ptr_为nullptr,不可再解引用,否则会触发空指针异常。
二、std::shared_ptr:共享式智能指针(最常用、有额外开销)
1. 核心设计思想
shared_ptr 实现共享所有权:多个 shared_ptr 可以共享同一个原生指针,当最后一个 shared_ptr 析构时,才会释放所管理的内存。其底层核心是引用计数(Reference Count),通过引用计数来跟踪当前有多少个 shared_ptr 共享该原生指针。
2. 底层核心数据结构(简化版)
shared_ptr 也是一个模板类,但底层结构比 unique_ptr 复杂,包含两个核心指针和一个引用计数块(控制块),存在额外堆内存开销(控制块)。
(1)整体结构(模板类)
template <typename T, typename Deleter = std::default_delete<T>, typename Allocator = std::allocator<T>>
class shared_ptr {
private:
T* ptr_; // 管理的原生指针(指向堆内存对象/数组,称为“对象指针”)
ControlBlock* ctrl_block_; // 指向控制块的指针(称为“控制块指针”)
public:
// 核心接口...
};
(2)控制块(Control Block):核心中的核心
控制块是 shared_ptr 底层的关键,每次创建一个新的 shared_ptr(非拷贝 / 移动)时,都会在堆上分配一个控制块,用于存储引用计数、删除器、分配器等信息。控制块的简化结构如下:
struct ControlBlock {
size_t use_count_; // 强引用计数:当前共享该原生指针的 shared_ptr 个数
size_t weak_count_; // 弱引用计数:当前观察该原生指针的 weak_ptr 个数
std::default_delete<T> del_; // 删除器(存储在控制块,而非 shared_ptr 本身)
std::allocator<T> alloc_; // 分配器(存储在控制块,用于释放控制块自身)
// 构造函数
ControlBlock() : use_count_(1), weak_count_(0), del_(), alloc_() {}
ControlBlock(Deleter del, Allocator alloc) : use_count_(1), weak_count_(0), del_(del), alloc_(alloc) {}
};
关键细节拆解:
- 两个指针的作用:
ptr_:直接指向管理的对象(如new int(10)),用于解引用(*up)、下标访问等操作。ctrl_block_:指向堆上的控制块,用于访问引用计数、删除器等信息,所有共享同一个对象的shared_ptr,其ctrl_block_都指向同一个控制块。
- 控制块的创建时机(必须牢记,避免引用计数错误):
- 直接用原生指针构造
shared_ptr时(shared_ptr<int> sp(new int(10))),创建新控制块。 - 用
std::make_shared构造时(推荐),一次性分配 “对象内存 + 控制块内存”,效率更高(减少一次堆分配)。 - 拷贝 / 移动
shared_ptr时,不创建新控制块,仅复制ctrl_block_指针,并递增强引用计数。 - 用
weak_ptr构造shared_ptr时,不创建新控制块,仅递增强引用计数。
- 直接用原生指针构造
- 引用计数的含义:
- 强引用计数(use_count_):跟踪当前有效的
shared_ptr个数,当use_count_减至 0 时,销毁管理的对象(调用删除器),但不销毁控制块。 - 弱引用计数(weak_count_):跟踪当前有效的
weak_ptr个数,当weak_count_减至 0 时,销毁控制块(释放控制块的堆内存)。
- 强引用计数(use_count_):跟踪当前有效的
- 删除器 / 分配器存储在控制块:
shared_ptr的删除器和分配器不是模板参数的一部分(与unique_ptr不同),而是存储在控制块中,这使得shared_ptr的拷贝 / 移动更灵活,但控制块占用更多内存。 - 额外堆内存开销:控制块需要在堆上分配,这是
shared_ptr相比unique_ptr的性能损耗点之一(还有引用计数的原子操作开销)。
3. 核心接口底层实现
(1)构造函数(两种核心场景)
场景 1:原生指针构造(创建新控制块)
explicit shared_ptr(T* ptr) : ptr_(ptr), ctrl_block_(nullptr) {
if (ptr != nullptr) {
// 1. 分配并构造控制块(堆上)
using CtrlBlockAlloc = std::allocator<ControlBlock>;
CtrlBlockAlloc alloc;
ctrl_block_ = alloc.allocate(1);
alloc.construct(ctrl_block_, ControlBlock()); // use_count_ = 1, weak_count_ = 0
}
}
- 若
ptr非空,在堆上分配控制块,并初始化强引用计数为 1,弱引用计数为 0。
场景 2:拷贝构造(共享控制块,递增强引用计数)
shared_ptr(const shared_ptr& other) noexcept : ptr_(other.ptr_), ctrl_block_(other.ctrl_block_) {
if (ctrl_block_ != nullptr) {
// 关键:原子递增强引用计数(线程安全)
std::atomic_fetch_add(&ctrl_block_->use_count_, 1);
}
}
- 复制
other的ptr_和ctrl_block_,然后原子递增强引用计数(atomic_fetch_add保证多线程环境下引用计数的正确性)。 - 引用计数的操作必须是原子操作,这是
shared_ptr线程安全的基础(但shared_ptr管理的对象本身不是线程安全的)。
场景 3:std::make_shared 构造(优化堆分配)
std::make_shared 是创建 shared_ptr 的推荐方式,底层一次性分配 “对象内存 + 控制块内存”,减少一次堆分配,效率更高:
template <typename T, typename... Args>
shared_ptr<T> make_shared(Args&&... args) {
// 1. 一次性分配对象内存 + 控制块内存(连续堆内存)
using Alloc = std::allocator<std::pair<ControlBlock, T>>;
Alloc alloc;
auto* mem = alloc.allocate(1); // 分配连续内存
// 2. 构造控制块(use_count_ = 1)
ControlBlock* ctrl_block = &mem->first;
new (ctrl_block) ControlBlock();
// 3. 构造对象(完美转发参数)
T* ptr = &mem->second;
new (ptr) T(std::forward<Args>(args)...);
// 4. 构造 shared_ptr 并返回
return shared_ptr<T>(ptr, ctrl_block);
}
- 优势:减少一次堆分配和一次堆释放,控制块和对象在连续内存中,缓存友好性更好。
- 劣势:对象内存和控制块内存绑定,即使
use_count_减至 0(对象销毁),控制块也需等待weak_count_减至 0 才会释放,可能导致 “内存延迟释放”。
(2)析构函数(核心:递减强引用计数,释放对象和控制块)
~shared_ptr() noexcept {
if (ctrl_block_ != nullptr) {
// 1. 原子递减强引用计数
size_t new_use_count = std::atomic_fetch_sub(&ctrl_block_->use_count_, 1);
if (new_use_count == 1) { // 递减后 use_count_ = 0(当前是最后一个 shared_ptr)
// 2. 销毁管理的对象(调用删除器)
if (ptr_ != nullptr) {
ctrl_block_->del_(ptr_);
}
// 3. 检查弱引用计数,若为 0,销毁控制块
if (ctrl_block_->weak_count_ == 0) {
// 释放控制块内存(使用控制块中的分配器)
using CtrlBlockAlloc = std::allocator<ControlBlock>;
CtrlBlockAlloc alloc = ctrl_block_->alloc_;
alloc.destroy(ctrl_block_);
alloc.deallocate(ctrl_block_, 1);
}
}
}
}
- 核心逻辑:只有当强引用计数减至 0 时,才销毁管理的对象;只有当弱引用计数也减至 0 时,才销毁控制块。
- 原子操作保证多线程环境下引用计数的正确性,避免竞态条件。
(3)移动构造(转移所有权,无引用计数操作)
shared_ptr(shared_ptr&& other) noexcept : ptr_(other.ptr_), ctrl_block_(other.ctrl_block_) {
// 关键:将 other 的指针置空,避免其析构时操作控制块
other.ptr_ = nullptr;
other.ctrl_block_ = nullptr;
}
- 移动构造不修改引用计数,仅转移
ptr_和ctrl_block_的所有权,效率与unique_ptr相当。
(4)重置(reset ())
void reset(T* new_ptr = nullptr) noexcept {
// 1. 保存当前控制块(用于后续析构)
ControlBlock* old_ctrl_block = ctrl_block_;
T* old_ptr = ptr_;
// 2. 重置当前指针
ptr_ = new_ptr;
ctrl_block_ = nullptr;
// 3. 若有旧控制块,递减强引用计数(逻辑同析构函数)
if (old_ctrl_block != nullptr) {
size_t new_use_count = std::atomic_fetch_sub(&old_ctrl_block_->use_count_, 1);
if (new_use_count == 1) {
if (old_ptr != nullptr) {
old_ctrl_block_->del_(old_ptr);
}
if (old_ctrl_block_->weak_count_ == 0) {
using CtrlBlockAlloc = std::allocator<ControlBlock>;
CtrlBlockAlloc alloc = old_ctrl_block_->alloc_;
alloc.destroy(old_ctrl_block_);
alloc.deallocate(old_ctrl_block_, 1);
}
}
}
// 4. 若有新指针,创建新控制块
if (new_ptr != nullptr) {
using CtrlBlockAlloc = std::allocator<ControlBlock>;
CtrlBlockAlloc alloc;
ctrl_block_ = alloc.allocate(1);
alloc.construct(ctrl_block_, ControlBlock());
}
}
4. 底层内存管理流程(示例)
{
// 1. 创建 sp1,分配控制块(use_count_=1,weak_count_=0)
shared_ptr<int> sp1(new int(10));
// 2. 拷贝 sp1 到 sp2,共享控制块,use_count_ 原子递增为 2
shared_ptr<int> sp2 = sp1;
// 3. 移动 sp1 到 sp3,sp1 置空,use_count_ 仍为 2
shared_ptr<int> sp3 = std::move(sp1);
// 4. sp2 重置,use_count_ 原子递减为 1
sp2.reset();
} // 离开作用域:
// - sp3 析构:use_count_ 原子递减为 0,销毁对象(int(10)),weak_count_=0,销毁控制块
// - sp2 析构:ctrl_block_ 已为空,无操作
// - sp1 析构:ctrl_block_ 已为空,无操作
- 只有最后一个
shared_ptr(sp3)析构时,才释放对象和控制块,实现共享所有权。
5. 底层坑点与细节
- 循环引用问题:这是
shared_ptr最经典的底层坑,两个shared_ptr互相引用,会导致强引用计数无法减至 0,内存泄漏。例如:struct Node { shared_ptr<Node> next; }; shared_ptr<Node> n1(new Node()); shared_ptr<Node> n2(new Node()); n1->next = n2; n2->next = n1; // 循环引用,n1 和 n2 的 use_count_ 均为 2,析构时无法减至 0- 解决方案:使用
weak_ptr打破循环引用(见下文)。
- 解决方案:使用
- 避免多个
shared_ptr管理同一个原生指针(非拷贝):这会创建多个独立的控制块,导致重复释放内存。例如:int* p = new int(10); shared_ptr<int> sp1(p); shared_ptr<int> sp2(p); // 错误:创建第二个控制块,sp1 和 sp2 析构时都会释放 p,重复释放 - 引用计数的原子操作仅保证计数本身安全:
shared_ptr的引用计数操作是线程安全的,但对管理的对象的读写操作不是线程安全的,需要手动加锁。 std::make_shared无法指定自定义删除器:若需要使用自定义删除器,只能用原生指针构造shared_ptr。
三、std::weak_ptr:弱引用智能指针(辅助 shared_ptr,无所有权)
1. 核心设计思想
weak_ptr 是辅助 shared_ptr 的弱引用智能指针,它不拥有对象的所有权,仅能观察 shared_ptr 管理的对象。其核心作用是打破 shared_ptr 的循环引用,同时避免悬垂指针。
weak_ptr 底层也依赖 shared_ptr 的控制块,仅跟踪弱引用计数,不影响强引用计数,因此不会阻止 shared_ptr 释放对象。
2. 底层核心数据结构(简化版)
weak_ptr 也是一个模板类,底层结构比 shared_ptr 简单,仅包含一个控制块指针(无对象指针,因为不拥有所有权):
template <typename T>
class weak_ptr {
private:
ControlBlock* ctrl_block_; // 指向 shared_ptr 的控制块(与 shared_ptr 共享同一个控制块)
public:
// 核心接口...
};
关键细节拆解:
- 无对象指针(
ptr_):weak_ptr不直接管理对象,因此不需要存储对象指针,仅需存储控制块指针,用于访问弱引用计数和检查对象是否存活。 - 不影响强引用计数:
weak_ptr的创建、拷贝、移动都只会修改弱引用计数(weak_count_),不会修改强引用计数(use_count_),因此不会阻止shared_ptr释放对象。 - 必须从
shared_ptr或weak_ptr构造:weak_ptr不能直接用原生指针构造,只能从shared_ptr或其他weak_ptr构造,保证其控制块指针的有效性。 - 无解引用操作(
*/->):因为weak_ptr不拥有对象所有权,对象可能已经被shared_ptr释放,因此不支持直接解引用,需要先通过lock()方法获取一个shared_ptr,再解引用。
3. 核心接口底层实现
(1)构造函数(从 shared_ptr 构造)
weak_ptr(const shared_ptr<T>& sp) noexcept : ctrl_block_(sp.ctrl_block_) {
if (ctrl_block_ != nullptr) {
// 原子递增弱引用计数
std::atomic_fetch_add(&ctrl_block_->weak_count_, 1);
}
}
- 复制
shared_ptr的控制块指针,原子递增弱引用计数,不修改强引用计数。
(2)拷贝构造(从其他 weak_ptr 构造)
weak_ptr(const weak_ptr& other) noexcept : ctrl_block_(other.ctrl_block_) {
if (ctrl_block_ != nullptr) {
std::atomic_fetch_add(&ctrl_block_->weak_count_, 1);
}
}
(3)析构函数(递减弱引用计数,销毁控制块)
~weak_ptr() noexcept {
if (ctrl_block_ != nullptr) {
// 1. 原子递减弱引用计数
size_t new_weak_count = std::atomic_fetch_sub(&ctrl_block_->weak_count_, 1);
if (new_weak_count == 1) { // 递减后 weak_count_ = 0
// 2. 检查强引用计数,若为 0,销毁控制块(对象已被销毁)
if (ctrl_block_->use_count_ == 0) {
using CtrlBlockAlloc = std::allocator<ControlBlock>;
CtrlBlockAlloc alloc = ctrl_block_->alloc_;
alloc.destroy(ctrl_block_);
alloc.deallocate(ctrl_block_, 1);
}
}
}
}
- 核心逻辑:仅递减弱引用计数,当弱引用计数减至 0 且强引用计数也为 0 时,销毁控制块。
- 因为
weak_ptr不拥有对象所有权,所以永远不会销毁管理的对象。
(4)lock() 方法(核心:获取有效的 shared_ptr)
lock() 是 weak_ptr 最常用的接口,底层逻辑是:检查对象是否存活(强引用计数 > 0),若存活,创建一个新的 shared_ptr,递增强引用计数;若不存活,返回一个空的 shared_ptr。
shared_ptr<T> lock() const noexcept {
if (ctrl_block_ != nullptr && ctrl_block_->use_count_ > 0) {
// 1. 原子递增强引用计数
std::atomic_fetch_add(&ctrl_block_->use_count_, 1);
// 2. 创建并返回 shared_ptr
return shared_ptr<T>(ctrl_block_->ptr_, ctrl_block_);
}
// 3. 对象已销毁,返回空 shared_ptr
return shared_ptr<T>();
}
lock()是线程安全的,通过检查强引用计数保证获取的shared_ptr管理的对象是存活的。
(5)expired() 方法(检查对象是否存活)
bool expired() const noexcept {
return (ctrl_block_ == nullptr) || (ctrl_block_->use_count_ == 0);
}
- 若返回
true,表示对象已被销毁;若返回false,表示对象仍存活。
4. 底层内存管理流程(打破循环引用示例)
struct Node {
weak_ptr<Node> next; // 用 weak_ptr 替代 shared_ptr,打破循环引用
};
{
shared_ptr<Node> n1(new Node());
shared_ptr<Node> n2(new Node());
n1->next = n2;
n2->next = n1; // n1->next 是 weak_ptr,不增加 n2 的 use_count_;n2->next 是 weak_ptr,不增加 n1 的 use_count_
} // 离开作用域:
// - n1 析构:use_count_ 减至 0,销毁 n1 对象,检查 weak_count_(n2->next 引用),递减为 0,销毁控制块
// - n2 析构:use_count_ 减至 0,销毁 n2 对象,检查 weak_count_(n1->next 引用),递减为 0,销毁控制块
weak_ptr不增加强引用计数,因此循环引用被打破,内存正常释放。
5. 底层坑点与细节
weak_ptr不能直接解引用:必须先调用lock()获取shared_ptr,再解引用,否则无法保证对象存活。lock()返回的shared_ptr为空时,不可解引用:若对象已被销毁,lock()返回空shared_ptr,解引用会触发空指针异常。weak_ptr也存在控制块开销:weak_ptr指向shared_ptr的控制块,因此也存在堆内存开销(控制块)。
四、三大智能指针底层对比(核心差异)
| 对比维度 | std::unique_ptr | std::shared_ptr | std::weak_ptr |
|---|---|---|---|
| 所有权 | 独占所有权 | 共享所有权 | 无所有权(弱引用) |
| 底层核心结构 | 栈上:对象指针 + 删除器 | 栈上:对象指针 + 控制块指针;堆上:控制块(引用计数 + 删除器) | 栈上:控制块指针(共享 shared_ptr 的控制块) |
| 引用计数 | 无 | 强引用计数(use_count_)+ 弱引用计数(weak_count_) | 仅跟踪弱引用计数(weak_count_) |
| 额外开销 | 无(与原生指针等价) | 有(控制块 + 原子操作) | 有(控制块) |
| 拷贝 / 移动 | 禁止拷贝,支持移动 | 支持拷贝(递增强引用)+ 移动 | 支持拷贝(递增弱引用)+ 移动 |
| 析构逻辑 | 直接释放对象(若非空) | 强引用计数为 0 时释放对象,弱引用计数为 0 时释放控制块 | 递减弱引用计数,弱引用计数为 0 且强引用计数为 0 时释放控制块 |
| 核心用途 | 单个对象的独占管理,性能优先 | 多个对象共享同一个资源,灵活优先 | 打破 shared_ptr 循环引用,观察对象存活状态 |
| 循环引用 | 无此问题 | 存在循环引用问题,导致内存泄漏 | 解决 shared_ptr 循环引用问题 |
五、核心总结
- 底层核心思想:三大智能指针均基于 RAII 机制,在析构时自动释放资源,避免手动
delete;差异的核心是所有权管理方式。 - unique_ptr 底层:独占所有权,栈上封装对象指针 + 删除器,无额外开销,禁用拷贝、支持移动,析构直接释放对象。
- shared_ptr 底层:共享所有权,依赖堆上控制块存储引用计数,拷贝递增强引用、析构递减强引用,最后一个释放对象,存在循环引用和额外开销问题。
- weak_ptr 底层:无所有权,共享 shared_ptr 的控制块,仅跟踪弱引用计数,通过
lock()获取有效 shared_ptr,核心用于打破循环引用。 - 性能优先级:
unique_ptr>shared_ptr>weak_ptr(weak_ptr通常配合shared_ptr使用),实际开发中优先使用unique_ptr,需要共享时使用shared_ptr,循环引用时用weak_ptr。 - 关键避坑:
shared_ptr避免循环引用、避免多个shared_ptr管理同一个原生指针;unique_ptr避免管理栈内存;weak_ptr解引用前必须lock()检查。
更多推荐


所有评论(0)