博主介绍:程序喵大人
- 35 - 资深C/C++/Rust/Android/iOS客户端开发
- 10年大厂工作经验
- 嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手
- 《C++20高级编程》《C++23高级编程》等多本书籍著译者
- 更多原创精品文章,首发gzh,见文末
- 👇👇记得订阅专栏,以防走丢👇👇
😉C++基础系列专栏
😃C语言基础系列专栏
🤣C++大佬养成攻略专栏
🤓C++训练营
👉🏻个人网站
C++ 内存管理的噩梦始于 new/delete 的手动配对,止于智能指针的自动化革新。从 RAII(资源获取即初始化)的核心理念,到 unique_ptr 的独占所有权,再到 shared_ptr 的引用计数共享机制,智能指针体系不仅解决了内存泄漏和悬空指针的顽疾,更通过类型系统明确了资源所有权语义。本文将深入剖析智能指针的实现原理,手写核心代码,助你彻底掌握这一现代 C++ 基石技术。
一、RAII:资源管理的哲学基石
RAII(Resource Acquisition Is Initialization,资源获取即初始化)是 C++ 最重要的设计理念之一。其核心思想简单而强大:资源的生命周期与对象的生命周期绑定。在对象构造时获取资源,在对象析构时自动释放资源,利用 C++ 栈对象的自动析构机制确保资源正确清理。
cpp
class FileHandler {
public:
FileHandler(const std::string& path) {
fileHandle = fopen(path.c_str(), "r"); // 构造即获取资源
}
~FileHandler() {
if (fileHandle) fclose(fileHandle); // 析构必释放资源
}
private:
FILE* fileHandle;
};
void processFile() {
FileHandler file("data.txt"); // 自动打开
// 使用文件...
} // 离开作用域,自动关闭,即使发生异常
RAII 的三大核心特性:
自动释放,告别手动 delete / close;
异常安全,即使发生异常也能正确回收资源;
禁止拷贝、支持移动,资源只能有唯一所有者。
这种设计让资源管理变得可预测、可维护,是智能指针诞生的思想基础。
二、unique_ptr:独占所有权的轻量级守护者
unique_ptr 体现了独占所有权的清晰语义。在任何时候,只有一个 unique_ptr 可以指向一个给定的对象。它通过禁止拷贝构造函数和拷贝赋值运算符,只提供移动构造函数和移动赋值运算符来实现所有权的唯一性。
手写 unique_ptr 的核心实现
cpp
template<typename T>
class MyUniquePtr {
private:
T* ptr_;
public:
MyUniquePtr() : ptr_(nullptr) {}
explicit MyUniquePtr(T* ptr) : ptr_(ptr) {}
MyUniquePtr(const MyUniquePtr&) = delete;
MyUniquePtr& operator=(const MyUniquePtr&) = delete;
MyUniquePtr(MyUniquePtr&& other) noexcept
: ptr_(other.ptr_) {
other.ptr_ = nullptr;
}
MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
if (this != &other) {
delete ptr_;
ptr_ = other.ptr_;
other.ptr_ = nullptr;
}
return *this;
}
~MyUniquePtr() {
delete ptr_;
}
T& operator*() const {
return *ptr_;
}
T* operator->() const {
return ptr_;
}
T* get() const {
return ptr_;
}
T* release() {
T* temp = ptr_;
ptr_ = nullptr;
return temp;
}
void reset(T* ptr = nullptr) {
delete ptr_;
ptr_ = ptr;
}
explicit operator bool() const {
return ptr_ != nullptr;
}
};
unique_ptr 的核心设计原则:
零开销抽象,性能几乎等同于裸指针;
异常安全,所有权转移过程不抛异常;
类型安全,在编译期防止错误使用。
这也是为什么在现代 C++ 中,unique_ptr 是默认首选的智能指针。
三、shared_ptr:引用计数的共享所有权模型
shared_ptr 允许多个指针共享同一个对象,通过引用计数机制管理对象生命周期。当最后一个 shared_ptr 被销毁时,对象才会被删除。其核心是控制块(Control Block)的设计。
引用计数控制块的内存布局
控制块是一个堆上的独立内存区域,通常包含以下内容:
- 强引用计数(use_count)
- 弱引用计数(weak_count)
- 指向被管理对象的指针
- 删除器(deleter)
cpp
template<typename T>
class ControlBlock {
public:
std::atomic<size_t> use_count{1};
std::atomic<size_t> weak_count{0};
T* ptr{nullptr};
std::function<void(T*)> deleter;
ControlBlock(T* p, const std::function<void(T*)>& del)
: ptr(p), deleter(del ? del : [](T* p) { delete p; }) {}
};
手写 shared_ptr 的核心实现
cpp
template<typename T>
class MySharedPtr {
private:
T* ptr_;
ControlBlock<T>* ctrl_block_;
void increment_ref() {
if (ctrl_block_) {
ctrl_block_->use_count.fetch_add(1, std::memory_order_relaxed);
}
}
void decrement_ref() {
if (ctrl_block_) {
if (ctrl_block_->use_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
ctrl_block_->deleter(ctrl_block_->ptr);
ctrl_block_->ptr = nullptr;
if (ctrl_block_->weak_count.load(std::memory_order_acquire) == 0) {
delete ctrl_block_;
}
}
}
}
public:
MySharedPtr() : ptr_(nullptr), ctrl_block_(nullptr) {}
explicit MySharedPtr(T* ptr)
: ptr_(ptr), ctrl_block_(new ControlBlock<T>(ptr, nullptr)) {}
template<typename Deleter>
MySharedPtr(T* ptr, Deleter del)
: ptr_(ptr), ctrl_block_(new ControlBlock<T>(ptr, del)) {}
MySharedPtr(const MySharedPtr& other)
: ptr_(other.ptr_), ctrl_block_(other.ctrl_block_) {
increment_ref();
}
MySharedPtr& operator=(const MySharedPtr& other) {
if (this != &other) {
decrement_ref();
ptr_ = other.ptr_;
ctrl_block_ = other.ctrl_block_;
increment_ref();
}
return *this;
}
MySharedPtr(MySharedPtr&& other) noexcept
: ptr_(other.ptr_), ctrl_block_(other.ctrl_block_) {
other.ptr_ = nullptr;
other.ctrl_block_ = nullptr;
}
~MySharedPtr() {
decrement_ref();
}
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
size_t use_count() const {
return ctrl_block_ ? ctrl_block_->use_count.load(std::memory_order_relaxed) : 0;
}
T* get() const { return ptr_; }
void reset(T* ptr = nullptr) {
decrement_ref();
if (ptr) {
ptr_ = ptr;
ctrl_block_ = new ControlBlock<T>(ptr, nullptr);
} else {
ptr_ = nullptr;
ctrl_block_ = nullptr;
}
}
};
四、引用计数的线程安全性考量
shared_ptr 的引用计数在多线程环境下必须是线程安全的,标准库通过原子操作来保证这一点。
关键实现细节包括:
- 使用 fetch_add / fetch_sub 操作引用计数
- 增加引用时使用 memory_order_relaxed
- 减少引用并可能释放资源时使用 memory_order_acq_rel
cpp
ctrl_block_->use_count.fetch_add(1, std::memory_order_relaxed);
if (ctrl_block_->use_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
// 释放资源
}
相比互斥锁,原子操作的性能开销要小得多,是 shared_ptr 设计中的关键优化点。
五、循环引用问题的产生与解决
当多个对象通过 shared_ptr 相互持有对方时,会形成循环引用,导致引用计数永远无法归零,从而引发内存泄漏。
典型的循环引用场景
cpp
class B;
class A {
public:
std::shared_ptr<B> b_ptr;
};
class B {
public:
std::shared_ptr<A> a_ptr;
};
void circularReference() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
}
使用 weak_ptr 打破循环
weak_ptr 不增加强引用计数,只作为观察者存在。
cpp
class B;
class A {
public:
std::shared_ptr<B> b_ptr;
};
class B {
public:
std::weak_ptr<A> a_ptr;
};
void fixedCircularReference() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
if (auto a_locked = b->a_ptr.lock()) {
// 对象仍然存在,可以安全访问
}
}
六、unique_ptr 与 shared_ptr 的核心差异
| 对比维度 | unique_ptr | shared_ptr |
|---|---|---|
| 所有权模型 | 独占所有权 | 共享所有权 |
| 拷贝语义 | 禁止拷贝,仅支持移动 | 支持拷贝 |
| 内存开销 | 单指针大小 | 指针 + 控制块 |
| 运行时开销 | 几乎为零 | 引用计数原子操作 |
| 线程安全 | 仅对象本身 | 引用计数线程安全 |
| 适用场景 | 明确唯一所有者 | 多对象共享资源 |
选择原则:
默认使用 unique_ptr;
确实需要共享时再使用 shared_ptr;
出现双向引用时引入 weak_ptr;
优先使用 make_unique / make_shared 以减少内存分配次数。
结语
智能指针是现代 C++ 内存管理的核心工具。RAII 提供了思想基础,unique_ptr 定义了清晰的独占所有权语义,shared_ptr 则通过引用计数实现了安全的共享模型,而 weak_ptr 负责解决循环引用这一经典难题。真正理解并掌握这些智能指针的实现原理,才能在工程实践中写出健壮、高性能、可维护的 C++ 代码。
码字不易,欢迎大家点赞,关注,评论,谢谢!