C++ 智能指针是 RAII(资源获取即初始化) 设计思想的核心应用,其底层目标是自动管理动态内存(或其他资源),避免内存泄漏、野指针、重复释放等问题。C++ 标准库提供了 3 种核心智能指针(C++11 及后续标准):std::unique_ptr、std::shared_ptr、std::weak_ptr,以及已被弃用的 std::auto_ptr。
一、核心设计思想:RAII(资源获取即初始化)
所有智能指针的底层核心都是 RAII,其本质是:
- 资源绑定对象生命周期:将动态内存(或文件句柄、socket 等资源)的“申请”与智能指针对象的“构造”绑定,“释放”与智能指针对象的“析构”绑定。
- 自动释放:当智能指针对象离开作用域(如函数返回、代码块结束、异常抛出)时,C++ 会自动调用其析构函数,完成资源释放,无需手动调用
delete。
RAII 解决的核心问题:
- 手动
delete遗漏导致的内存泄漏。 - 异常抛出时,
delete未执行导致的资源泄漏。 - 重复
delete或释放野指针导致的未定义行为。
智能指针的底层本质:封装裸指针(T),并重载 operator*、operator-> 等运算符模拟指针行为,同时在析构函数中实现资源释放逻辑*。
二、std::unique_ptr 底层解析:独占所有权
std::unique_ptr 是 独占式智能指针,核心特性是“同一时间仅一个 unique_ptr 拥有对资源的所有权”,禁止拷贝、支持移动,底层结构最简单、性能最优(接近裸指针)。
2.1 底层核心结构
unique_ptr 的底层是 “裸指针 + 可选删除器” 的紧凑结构,无额外引用计数开销,内存大小与裸指针基本一致(删除器为无状态时完全一致)。
简化版底层结构(标准库实现逻辑)
template <typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
private:
T* ptr_; // 指向动态内存的裸指针(核心数据)
Deleter deleter_; // 删除器(默认是 std::default_delete<T>)
public:
// 1. 构造函数:绑定裸指针(资源申请)
explicit unique_ptr(T* p = nullptr) noexcept : ptr_(p) {}
// 2. 析构函数:自动释放资源(核心)
~unique_ptr() noexcept {
if (ptr_ != nullptr) {
deleter_(ptr_); // 调用删除器释放资源
}
}
// 3. 禁止拷贝:删除拷贝构造和拷贝赋值运算符(C++11 语法)
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
// 4. 支持移动:移动构造和移动赋值(转移所有权)
unique_ptr(unique_ptr&& other) noexcept
: ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
other.ptr_ = nullptr; // 原指针置空,避免重复释放
}
unique_ptr& operator=(unique_ptr&& other) noexcept {
if (this != &other) {
// 先释放当前资源
if (ptr_ != nullptr) {
deleter_(ptr_);
}
// 转移所有权
ptr_ = other.ptr_;
deleter_ = std::move(other.deleter_);
other.ptr_ = nullptr;
}
return *this;
}
// 5. 模拟指针行为:重载 operator* 和 operator->
T& operator*() const noexcept { return *ptr_; }
T* operator->() const noexcept { return ptr_; }
// 6. 其他核心接口
T* get() const noexcept { return ptr_; } // 获取裸指针(谨慎使用)
void reset(T* p = nullptr) noexcept { // 重置指针(释放旧资源,绑定新资源)
if (ptr_ != nullptr) {
deleter_(ptr_);
}
ptr_ = p;
}
T* release() noexcept { // 释放所有权(返回裸指针,自身置空)
T* temp = ptr_;
ptr_ = nullptr;
return temp;
}
};
关键底层细节
- 裸指针
ptr_:直接指向动态分配的对象(如new T分配的内存),是unique_ptr唯一的核心数据,无额外内存开销。 - 删除器
deleter_:- 默认是
std::default_delete<T>,其底层实现为delete ptr(普通指针)或delete[] ptr(数组指针)。 - 支持自定义删除器(如释放数组、文件句柄、第三方库资源),删除器的类型是模板参数,编译时确定,无类型擦除开销。
- 默认是
2.2 所有权模型:独占与移动
unique_ptr 的核心是 “独占所有权”,底层通过以下机制保证:
- 禁止拷贝:显式删除拷贝构造和拷贝赋值运算符(
= delete),编译时直接报错,避免多个unique_ptr指向同一资源。 - 支持移动:通过移动构造/赋值转移所有权,底层逻辑是“拷贝原指针 → 原指针置空”,确保仅有一个
unique_ptr持有有效指针。
移动语义底层示例
std::unique_ptr<int> p1(new int(10)); // p1.ptr_ = 0x1234(假设地址)
std::unique_ptr<int> p2 = std::move(p1); // 移动构造
// 底层状态:p2.ptr_ = 0x1234,p1.ptr_ = nullptr
p1.reset(); // 无操作(ptr_ 已为空)
p2.reset(); // 调用 delete 0x1234,释放资源
2.3 数组特化版本(unique_ptr<T[]>)
unique_ptr 提供数组特化版本,底层通过模板特化区分普通指针和数组指针,核心差异在删除器:
// 数组特化版本:默认删除器为 std::default_delete<T[]>(调用 delete[])
template <typename T, typename Deleter>
class unique_ptr<T[], Deleter> {
private:
T* ptr_;
Deleter deleter_;
public:
~unique_ptr() noexcept {
if (ptr_ != nullptr) {
deleter_(ptr_); // 数组版本:delete[] ptr_
}
}
T& operator[](size_t idx) const noexcept { return ptr_[idx]; } // 重载 [] 运算符
};
// 使用示例
std::unique_ptr<int[]> p(new int[5]); // 数组版本,析构时 delete[]
p[0] = 10; // 支持 [] 访问
2.4 底层性能开销
unique_ptr 是 零额外开销(Zero Overhead) 设计:
- 内存开销:与裸指针完全一致(当删除器是无状态时,如默认删除器、无捕获 lambda);若删除器有状态(如捕获变量的 lambda),则内存开销增加删除器的状态大小。
- 时间开销:构造、析构、移动操作均为 O(1),无原子操作或复杂逻辑,性能接近裸指针。
2.5 自定义删除器底层实现
自定义删除器的本质是“提供一个可调用对象(函数指针、lambda、函数对象),在析构时调用该对象释放资源”。底层分为两种场景:
场景 1:无状态删除器(无额外开销)
// 函数对象删除器(无状态)
struct FileDeleter {
void operator()(FILE* fp) const {
if (fp != nullptr) {
fclose(fp); // 释放文件句柄(而非 delete)
}
}
};
// 底层:FileDeleter 无成员变量,unique_ptr 内存大小 = sizeof(FILE*)
std::unique_ptr<FILE, FileDeleter> file_ptr(fopen("test.txt", "r"));
场景 2:有状态删除器(增加内存开销)
// 捕获变量的 lambda 作为删除器(有状态)
int log_level = 1;
auto deleter = [log_level](int* p) {
if (log_level > 0) {
std::cout << "释放内存:" << p << std::endl;
}
delete p;
};
// 底层:unique_ptr 内存大小 = sizeof(int*) + 捕获变量大小(此处为 sizeof(int))
std::unique_ptr<int, decltype(deleter)> p(new int(10), deleter);
三、std::shared_ptr 底层解析:共享所有权
std::shared_ptr 是 共享式智能指针,核心特性是“多个 shared_ptr 可共享同一资源的所有权”,底层通过 引用计数(Reference Counting) 管理资源生命周期,支持拷贝和移动。
3.1 底层核心结构:双指针 + 控制块
shared_ptr 的底层并非简单封装裸指针,而是由 “数据指针(data pointer)+ 控制块指针(control block pointer)” 组成,核心复杂度集中在控制块。
1. 双指针设计
| 指针类型 | 作用 |
|---|---|
| 数据指针(T*) | 指向实际动态分配的对象(如 new T 生成的对象),用于访问对象。 |
| 控制块指针(CB*) | 指向一个独立分配的“控制块”(Control Block),存储引用计数、删除器、分配器等元数据。 |
2. 控制块(Control Block)底层结构
控制块是 shared_ptr 的核心,所有共享同一资源的 shared_ptr 都会指向同一个控制块,底层结构如下(简化版):
struct ControlBlock {
std::atomic<size_t> shared_count; // 强引用计数:当前共享资源的 shared_ptr 数量
std::atomic<size_t> weak_count; // 弱引用计数:当前指向该控制块的 weak_ptr 数量
std::function<void(void*)> deleter; // 删除器(类型擦除,支持任意可调用对象)
std::function<void(void*)> allocator; // 分配器(用于释放控制块自身)
// 构造函数:初始化引用计数和删除器
ControlBlock(void* ptr, decltype(deleter) d, decltype(allocator) a)
: shared_count(1), weak_count(0), deleter(d), allocator(a) {}
// 析构控制块(仅当 shared_count 和 weak_count 均为 0 时调用)
~ControlBlock() {
allocator(this); // 释放控制块自身内存
}
};
3. shared_ptr 简化版底层实现
template <typename T>
class shared_ptr {
private:
T* data_ptr_; // 数据指针:指向实际对象
ControlBlock* cb_ptr_; // 控制块指针:指向共享控制块
public:
// 1. 构造函数(从裸指针创建,生成控制块)
explicit shared_ptr(T* p = nullptr) {
if (p != nullptr) {
// 分配控制块,初始化强引用计数=1,删除器=默认 delete
cb_ptr_ = new ControlBlock(
p,
[](void* ptr) { delete static_cast<T*>(ptr); }, // 默认删除器
[](void* cb) { delete static_cast<ControlBlock*>(cb); } // 控制块分配器
);
data_ptr_ = p;
} else {
data_ptr_ = nullptr;
cb_ptr_ = nullptr;
}
}
// 2. 拷贝构造函数(共享所有权,强引用计数+1)
shared_ptr(const shared_ptr& other) noexcept {
if (other.cb_ptr_ != nullptr) {
data_ptr_ = other.data_ptr_;
cb_ptr_ = other.cb_ptr_;
// 原子操作:强引用计数+1(保证线程安全)
cb_ptr_->shared_count.fetch_add(1, std::memory_order_relaxed);
} else {
data_ptr_ = nullptr;
cb_ptr_ = nullptr;
}
}
// 3. 移动构造函数(转移所有权,无引用计数操作)
shared_ptr(shared_ptr&& other) noexcept {
data_ptr_ = other.data_ptr_;
cb_ptr_ = other.cb_ptr_;
// 原对象置空,避免析构时操作控制块
other.data_ptr_ = nullptr;
other.cb_ptr_ = nullptr;
}
// 4. 析构函数(核心:强引用计数-1,为0则释放资源)
~shared_ptr() noexcept {
if (cb_ptr_ != nullptr) {
// 原子操作:强引用计数-1
size_t new_count = cb_ptr_->shared_count.fetch_sub(1, std::memory_order_acq_rel);
if (new_count == 1) {
// 强引用计数为0,释放实际对象
cb_ptr_->deleter(data_ptr_);
// 检查弱引用计数,若为0则释放控制块
if (cb_ptr_->weak_count == 0) {
delete cb_ptr_;
}
}
}
}
// 5. 重载 operator* 和 operator->(模拟指针行为)
T& operator*() const noexcept { return *data_ptr_; }
T* operator->() const noexcept { return data_ptr_; }
// 6. 核心接口
size_t use_count() const noexcept { // 获取强引用计数
return (cb_ptr_ != nullptr) ? cb_ptr_->shared_count.load() : 0;
}
bool unique() const noexcept { // 是否唯一拥有所有权
return use_count() == 1;
}
T* get() const noexcept { return data_ptr_; }
};
3.2 核心机制:引用计数的原子操作
shared_ptr 的引用计数(shared_count 和 weak_count)是 原子变量(std::atomic<size_t>),底层通过原子操作保证多线程环境下的安全性,避免竞争条件。
引用计数操作的线程安全语义
- 强引用计数增减:使用
std::memory_order_acq_rel内存序(获取-释放语义),确保对引用计数的修改在多线程间可见,避免重排序导致的未定义行为。 - 弱引用计数增减:同样使用原子操作,确保
weak_ptr的操作不干扰shared_ptr的资源管理。
引用计数生命周期流程(示例)
// 1. 创建 shared_ptr,控制块 shared_count=1,weak_count=0
std::shared_ptr<int> p1(new int(10));
// 2. 拷贝 p1,shared_count 原子+1 → 2
std::shared_ptr<int> p2 = p1;
// 3. 移动 p2,shared_count 不变(仍为2),p2 置空
std::shared_ptr<int> p3 = std::move(p2);
// 4. p3 析构,shared_count 原子-1 → 1
p3.reset();
// 5. p1 析构,shared_count 原子-1 → 0 → 释放对象(delete int(10))
// 此时 weak_count=0 → 释放控制块
3.3 控制块的创建时机
控制块的创建是 shared_ptr 底层的关键细节,确保所有共享资源的 shared_ptr 都指向同一个控制块,避免“多个控制块管理同一资源”导致的重复释放。控制块仅在以下 3 种场景创建:
- 通过裸指针构造
shared_ptr(如shared_ptr<T> p(new T)):直接创建控制块,绑定裸指针和默认删除器。 - 通过
std::make_shared<T>创建(推荐):make_shared会一次性分配“对象 + 控制块”的连续内存,效率更高(减少一次内存分配),且控制块与对象生命周期绑定。 - 通过
unique_ptr移动构造shared_ptr:从unique_ptr的删除器和裸指针创建控制块,转移所有权。
关键禁忌:避免用同一裸指针创建多个 shared_ptr
int* raw_ptr = new int(10);
std::shared_ptr<int> p1(raw_ptr); // 创建控制块 A,shared_count=1
std::shared_ptr<int> p2(raw_ptr); // 创建控制块 B,shared_count=1
// 析构时:p1 释放 raw_ptr → p2 释放已释放的裸指针 → 未定义行为(双重释放)
底层原因:两个 shared_ptr 各自创建独立控制块,引用计数独立,导致双重释放。
3.4 类型擦除:删除器的灵活实现
shared_ptr 支持任意类型的删除器(函数指针、lambda、函数对象),即使删除器类型不同,shared_ptr 的类型仍可一致(如 shared_ptr<FILE> 可搭配不同的文件释放逻辑)。底层实现依赖 类型擦除(Type Erasure):
- 删除器存储为
std::function<void(void*)>,将删除器的类型信息“擦除”,仅保留调用接口。 - 类型擦除的代价:
std::function有少量内存开销(通常是 3 个指针大小),且调用时有轻微的间接跳转开销(远小于内存泄漏风险)。
自定义删除器示例(底层类型擦除)
// 函数指针删除器
void free_int(int* p) { delete p; }
std::shared_ptr<int> p1(new int(10), free_int); // 删除器类型擦除为 function
// lambda 删除器(捕获变量)
auto deleter = [](FILE* fp) { fclose(fp); };
std::shared_ptr<FILE> p2(fopen("test.txt", "r"), deleter); // 同样类型擦除
3.5 底层性能开销
shared_ptr 的开销高于 unique_ptr,主要来自 3 方面:
-
内存开销:
- 双指针(
data_ptr_ + cb_ptr_):通常是 2 * sizeof(void*)(64 位系统下 16 字节)。 - 控制块:包含两个原子变量 + 两个
std::function,内存开销约 40~64 字节(取决于系统和编译器)。 - 注:
std::make_shared可减少一次内存分配,且对象与控制块连续存储,缓存局部性更好,性能更优。
- 双指针(
-
时间开销:
- 拷贝/赋值:原子操作(
fetch_add/fetch_sub)的开销(约为普通整数操作的 10~100 倍,但仍是纳秒级)。 - 析构:原子减 1 + 条件判断(释放对象/控制块),开销轻微。
- 拷贝/赋值:原子操作(
3.6 线程安全特性
shared_ptr 的线程安全分为两层,底层实现决定了其安全边界:
- 引用计数的线程安全:强引用计数和弱引用计数的原子操作保证了“多线程同时拷贝、赋值、析构
shared_ptr”是安全的,不会导致引用计数错乱。 - 对象的线程安全:
shared_ptr不保证所指向对象的线程安全!多线程同时访问对象的成员(如p->set_val(5)),需手动加锁(如std::mutex),否则会导致数据竞争。
线程安全示例与禁忌
std::shared_ptr<int> p = std::make_shared<int>(10);
// 安全:多线程同时拷贝 p(仅操作引用计数,原子操作)
std::thread t1([p]() { auto q = p; });
std::thread t2([p]() { auto q = p; });
// 不安全:多线程同时修改对象内容(无同步)
std::thread t3([p]() { *p = 20; });
std::thread t4([p]() { *p = 30; }); // 数据竞争,未定义行为
四、std::weak_ptr 底层解析:弱引用观察者
std::weak_ptr 是 弱引用智能指针,核心特性是“不拥有资源所有权,仅观察 shared_ptr 管理的资源”,底层用于解决 shared_ptr 的 循环引用 问题。
4.1 底层核心结构
weak_ptr 的底层结构比 shared_ptr 更简单:仅存储控制块指针(ControlBlock),无数据指针*。因为 weak_ptr 不直接访问对象,仅通过控制块观察资源状态。
简化版底层实现
template <typename T>
class weak_ptr {
private:
ControlBlock* cb_ptr_; // 仅指向控制块,无数据指针
public:
// 1. 从 shared_ptr 构造(弱引用计数+1)
weak_ptr(const shared_ptr<T>& other) noexcept {
if (other.cb_ptr_ != nullptr) {
cb_ptr_ = other.cb_ptr_;
// 原子操作:弱引用计数+1
cb_ptr_->weak_count.fetch_add(1, std::memory_order_relaxed);
} else {
cb_ptr_ = nullptr;
}
}
// 2. 析构函数(弱引用计数-1,为0且强引用为0则释放控制块)
~weak_ptr() noexcept {
if (cb_ptr_ != nullptr) {
size_t new_weak_count = cb_ptr_->weak_count.fetch_sub(1, std::memory_order_acq_rel);
// 仅当强引用计数为0且弱引用计数为0时,释放控制块
if (new_weak_count == 1 && cb_ptr_->shared_count == 0) {
delete cb_ptr_;
}
}
}
// 3. 核心接口:lock() → 升级为 shared_ptr(关键)
shared_ptr<T> lock() const noexcept {
if (cb_ptr_ == nullptr) {
return shared_ptr<T>();
}
// 原子操作:检查强引用计数是否 > 0(资源是否存活)
if (cb_ptr_->shared_count.load(std::memory_order_acquire) == 0) {
return shared_ptr<T>(); // 资源已释放,返回空 shared_ptr
}
// 强引用计数+1,返回有效的 shared_ptr
return shared_ptr<T>(data_ptr_from_cb(), cb_ptr_); // 内部构造,避免创建新控制块
}
// 4. 其他接口
bool expired() const noexcept { // 检查资源是否已释放(强引用计数为0)
return (cb_ptr_ == nullptr) || (cb_ptr_->shared_count.load() == 0);
}
size_t use_count() const noexcept { // 获取当前强引用计数
return (cb_ptr_ != nullptr) ? cb_ptr_->shared_count.load() : 0;
}
};
4.2 核心机制:弱引用计数与资源观察
weak_ptr 的底层核心是 “弱引用计数”,其生命周期与控制块绑定,而非资源本身:
weak_ptr拷贝/构造时,仅增加 弱引用计数(weak_count),不影响强引用计数(shared_count)。weak_ptr不直接访问资源,必须通过lock()升级为shared_ptr:- 若资源存活(
shared_count > 0):lock()会原子性增加shared_count,返回有效shared_ptr。 - 若资源已释放(
shared_count == 0):lock()返回空shared_ptr,避免野指针访问。
- 若资源存活(
4.3 解决循环引用的底层逻辑
shared_ptr 的循环引用会导致强引用计数无法减为 0,资源泄漏。weak_ptr 因不增加强引用计数,可打破循环。
循环引用示例(无 weak_ptr)
struct A;
struct B {
std::shared_ptr<A> ptr; // B 持有 A 的强引用
};
struct A {
std::shared_ptr<B> ptr; // A 持有 B 的强引用
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->ptr = b; // A 的强引用计数=1 → 2
b->ptr = a; // B 的强引用计数=1 → 2
// 析构时:a 和 b 的强引用计数均减为1,无法继续减为0 → 资源泄漏
}
底层原因:强引用闭环导致 shared_count 无法归零。
用 weak_ptr 打破循环(底层逻辑)
struct A;
struct B {
std::weak_ptr<A> ptr; // B 持有 A 的弱引用(不增加 shared_count)
};
struct A {
std::shared_ptr<B> ptr; // A 持有 B 的强引用
};
int main() {
auto a = std::make_shared<A>(); // A.shared_count=1
auto b = std::make_shared<B>(); // B.shared_count=1
a->ptr = b; // B.shared_count=2
b->ptr = a; // A.shared_count 仍为1(weak_ptr 不增加)
// 析构流程:
// 1. a 析构 → A.shared_count=0 → 释放 A 对象 → A.ptr(b)的 shared_count=1
// 2. b 析构 → B.shared_count=0 → 释放 B 对象 → B.ptr(weak_ptr)析构
// 3. B.ptr 的 weak_count 减为0,且 A 的 shared_count=0 → 释放控制块
// 资源完全释放,无泄漏
}
4.4 底层性能开销与线程安全
- 内存开销:仅一个控制块指针(
cb_ptr_),大小为sizeof(void*)(64 位系统 8 字节),无额外开销。 - 时间开销:
- 拷贝/析构:仅原子操作修改弱引用计数,开销与
shared_ptr的引用计数操作相当。 lock():原子检查强引用计数 + 可能的强引用计数增加,开销轻微。
- 拷贝/析构:仅原子操作修改弱引用计数,开销与
- 线程安全:与
shared_ptr一致,弱引用计数的操作是线程安全的,但通过lock()获得的shared_ptr所指向的对象仍需手动同步。
五、已弃用的 std::auto_ptr 底层缺陷
std::auto_ptr 是 C++98 引入的早期智能指针,因底层设计缺陷被 C++11 弃用,核心缺陷在于 “拷贝语义不合理”。
底层结构与拷贝逻辑
auto_ptr 的底层结构与 unique_ptr 类似(裸指针 + 默认删除器),但拷贝构造/赋值的底层逻辑是“转移所有权”,而非禁止拷贝:
template <typename T>
class auto_ptr {
private:
T* ptr_;
public:
// 拷贝构造函数:转移所有权(原对象置空)
auto_ptr(auto_ptr& other) noexcept : ptr_(other.ptr_) {
other.ptr_ = nullptr; // 原对象指针置空
}
// 拷贝赋值运算符:转移所有权(原对象释放资源,原对象置空)
auto_ptr& operator=(auto_ptr& other) noexcept {
if (this != &other) {
delete ptr_; // 释放当前资源
ptr_ = other.ptr_; // 转移所有权
other.ptr_ = nullptr;
}
return *this;
}
};
底层缺陷导致的问题
- 拷贝后原对象悬空:
std::auto_ptr<int> p1(new int(10)); std::auto_ptr<int> p2 = p1; // p1.ptr_ = nullptr,p2.ptr_ = 0x1234 *p1 = 20; // 访问空指针 → 未定义行为 - 无法放入容器:容器要求元素支持拷贝,
auto_ptr的拷贝会转移所有权,导致容器中元素悬空(如vector<auto_ptr<int>>扩容时拷贝元素,原元素置空)。 - 数组支持缺陷:
auto_ptr无数组特化版本,析构时调用delete而非delete[],释放数组会导致内存泄漏。
unique_ptr 通过“禁止拷贝、支持移动”的底层设计,完美解决了 auto_ptr 的缺陷。
六、四种智能指针底层对比总结
| 特性 | unique_ptr | shared_ptr | weak_ptr | auto_ptr(已弃用) |
|---|---|---|---|---|
| 底层结构 | 裸指针 + 模板删除器(无类型擦除) | 双指针(数据+控制块)+ 控制块(引用计数+类型擦除删除器) | 控制块指针(无数据指针) | 裸指针 + 默认删除器 |
| 所有权模型 | 独占所有权(不可拷贝,可移动) | 共享所有权(可拷贝,可移动) | 无所有权(仅观察) | 独占所有权(拷贝转移,设计缺陷) |
| 引用计数 | 无 | 强引用计数(原子)+ 弱引用计数(原子) | 仅访问控制块的引用计数 | 无 |
| 内存开销 | 裸指针大小(删除器无状态时) | 双指针 + 控制块(较大) | 单指针(最小) | 裸指针大小 |
| 时间开销 | 零额外开销(接近裸指针) | 原子操作 + 类型擦除(轻微开销) | 原子操作(轻微开销) | 零额外开销 |
| 线程安全 | 无(仅对象本身线程安全,无共享状态) | 引用计数线程安全,对象需手动同步 | 弱引用计数线程安全,对象需手动同步 | 无 |
| 核心场景 | 独占资源、局部变量、容器元素 | 共享资源、长生命周期对象 | 打破循环引用、观察共享资源 | 无(已弃用) |
| 自定义删除器 | 模板参数(编译时确定,无开销) | 类型擦除(std::function,轻微开销) | 不支持(依赖 shared_ptr 的删除器) | 不支持 |
七、核心使用原则与底层注意事项
- 优先使用
unique_ptr:无额外开销,独占所有权,适合大多数场景(如函数返回动态对象、容器元素)。 shared_ptr仅用于共享所有权:避免滥用(如无需共享时用shared_ptr会增加不必要的开销和循环引用风险)。make_shared优先于new构造shared_ptr:一次性分配对象和控制块,减少内存分配次数,提升缓存局部性。- 避免用裸指针初始化多个
shared_ptr:会创建多个控制块,导致双重释放。 weak_ptr仅用于打破shared_ptr循环引用:不单独使用(无法直接访问对象),需通过lock()升级为shared_ptr。- 智能指针不管理栈内存:智能指针的析构函数会调用
delete,若绑定栈内存地址,会导致释放栈内存的未定义行为。 - 避免
get()返回的裸指针被其他智能指针管理:get()仅用于临时访问,若将其交给其他智能指针,会导致重复释放。
八、总结
C++ 智能指针的底层设计围绕 “RAII 思想 + 所有权管理” 展开:
unique_ptr以“独占所有权 + 移动语义”为核心,追求零额外开销,是默认首选。shared_ptr以“共享所有权 + 原子引用计数”为核心,支持拷贝,适合资源共享场景,代价是轻微的内存和时间开销。weak_ptr以“弱引用 + 控制块观察”为核心,解决shared_ptr的循环引用问题,无额外所有权负担。
1937

被折叠的 条评论
为什么被折叠?



