智能指针原理
智能指针是 C++ RAII 的典型应用,彻底解决了裸指针的内存管理问题。理解其底层实现,才能用对、用好。
为什么需要智能指针
cpp
// 裸指针的问题
void bad() {
int* p = new int(42);
if (error()) return; // 泄漏!
throw std::exception(); // 泄漏!
delete p;
}
// 智能指针:自动管理
void good() {
auto p = std::make_unique<int>(42);
if (error()) return; // 自动释放
throw std::exception(); // 自动释放
}unique_ptr:独占所有权
cpp
#include <memory>
// 基本用法
auto p = std::make_unique<int>(42);
std::cout << *p << "\n"; // 42
// 不可拷贝,只能移动
auto p2 = std::move(p); // p 变为 nullptr
// auto p3 = p2; // 编译错误!
// 自定义删除器
auto file_deleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr<FILE, decltype(file_deleter)> fp(fopen("a.txt", "r"), file_deleter);
// 数组版本
auto arr = std::make_unique<int[]>(10);
arr[0] = 1; // 支持下标访问
// 释放所有权(返回裸指针,需手动管理)
int* raw = p2.release();
delete raw;
// 重置
p2.reset(new int(99)); // 释放旧资源,持有新资源
p2.reset(); // 释放,变为 nullptrunique_ptr 底层实现
cpp
// 简化版 unique_ptr 实现
template<typename T, typename Deleter = std::default_delete<T>>
class UniquePtr {
T* ptr_ = nullptr;
Deleter del_;
public:
explicit UniquePtr(T* p = nullptr) : ptr_(p) {}
~UniquePtr() {
if (ptr_) del_(ptr_);
}
// 禁止拷贝
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;
// 允许移动
UniquePtr(UniquePtr&& other) noexcept : ptr_(other.ptr_) {
other.ptr_ = nullptr;
}
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
T* get() const { return ptr_; }
explicit operator bool() const { return ptr_ != nullptr; }
};
// 零开销:sizeof(UniquePtr<T>) == sizeof(T*)(空基类优化)shared_ptr:共享所有权
cpp
#include <memory>
// 基本用法
auto sp1 = std::make_shared<int>(42);
auto sp2 = sp1; // 共享所有权,引用计数 = 2
auto sp3 = sp1; // 引用计数 = 3
std::cout << sp1.use_count() << "\n"; // 3
sp2.reset(); // 引用计数 = 2
{
auto sp4 = sp1; // 引用计数 = 3
} // sp4 析构,引用计数 = 2
// 最后一个 shared_ptr 析构时,释放对象shared_ptr 底层实现
cpp
// shared_ptr 的内存布局
// make_shared 版本(推荐):一次分配,控制块和对象在一起
//
// ┌─────────────────────────────────────┐
// │ 控制块 Control Block │
// │ ├── strong_count (引用计数) │
// │ ├── weak_count (弱引用计数) │
// │ └── deleter / allocator │
// ├─────────────────────────────────────┤
// │ 对象数据 T │
// └─────────────────────────────────────┘
//
// shared_ptr<T> 本身包含两个指针:
// ├── ptr_ → 指向对象
// └── ctrl_ → 指向控制块
// 简化实现
template<typename T>
class SharedPtr {
struct ControlBlock {
std::atomic<int> strong_count{1};
std::atomic<int> weak_count{1}; // 包含一个隐式弱引用
virtual void destroy() = 0;
virtual ~ControlBlock() = default;
};
T* ptr_ = nullptr;
ControlBlock* ctrl_ = nullptr;
public:
SharedPtr(const SharedPtr& other) : ptr_(other.ptr_), ctrl_(other.ctrl_) {
if (ctrl_) ctrl_->strong_count.fetch_add(1);
}
~SharedPtr() {
if (!ctrl_) return;
if (ctrl_->strong_count.fetch_sub(1) == 1) {
ctrl_->destroy(); // 析构对象
if (ctrl_->weak_count.fetch_sub(1) == 1) {
delete ctrl_; // 释放控制块
}
}
}
};shared_ptr 的陷阱
cpp
// 陷阱 1:循环引用 → 内存泄漏
struct Node {
std::shared_ptr<Node> next; // 循环引用!
std::shared_ptr<Node> prev;
};
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->prev = a; // a 和 b 互相持有,引用计数永远不为 0
// 解决:用 weak_ptr 打破循环
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 弱引用,不增加引用计数
};
// 陷阱 2:从裸指针构造多个 shared_ptr
int* raw = new int(42);
std::shared_ptr<int> sp1(raw);
std::shared_ptr<int> sp2(raw); // 两个独立控制块!double free!
// 正确:用 make_shared 或从已有 shared_ptr 拷贝
auto sp = std::make_shared<int>(42);
auto sp2 = sp; // 共享同一控制块
// 陷阱 3:在类内部获取 this 的 shared_ptr
class Foo : public std::enable_shared_from_this<Foo> {
public:
std::shared_ptr<Foo> get_self() {
return shared_from_this(); // 正确!
// return std::shared_ptr<Foo>(this); // 错误!
}
};weak_ptr:弱引用
cpp
#include <memory>
auto sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp; // 不增加引用计数
std::cout << wp.use_count() << "\n"; // 1(只有 sp)
std::cout << wp.expired() << "\n"; // false
// 使用前必须 lock(),获取临时 shared_ptr
if (auto locked = wp.lock()) {
std::cout << *locked << "\n"; // 42
} else {
std::cout << "对象已销毁\n";
}
sp.reset(); // 释放对象
std::cout << wp.expired() << "\n"; // true
auto locked = wp.lock(); // 返回空 shared_ptr缓存场景
cpp
// weak_ptr 经典用途:缓存(不阻止对象被销毁)
class Cache {
std::unordered_map<int, std::weak_ptr<Data>> cache_;
public:
std::shared_ptr<Data> get(int id) {
auto it = cache_.find(id);
if (it != cache_.end()) {
if (auto sp = it->second.lock()) {
return sp; // 缓存命中
}
}
// 缓存未命中或已过期,重新加载
auto sp = load_data(id);
cache_[id] = sp;
return sp;
}
};性能对比
cpp
// unique_ptr:零开销,与裸指针完全等价
// sizeof(unique_ptr<T>) == sizeof(T*)
// shared_ptr:有开销
// sizeof(shared_ptr<T>) == 2 * sizeof(void*)(两个指针)
// 引用计数操作是原子操作(线程安全但有开销)
// make_shared 比 shared_ptr(new T) 少一次内存分配
// 性能建议:
// 1. 优先用 unique_ptr,需要共享时再用 shared_ptr
// 2. 用 make_shared/make_unique,不要直接 new
// 3. 函数参数传 T& 或 T*(不转移所有权时),不要传智能指针
// 4. 热路径避免 shared_ptr 拷贝(原子操作有开销)
void process(const Widget& w); // 推荐:不涉及所有权
void process(Widget* w); // 可以:可能为 nullptr
void process(std::unique_ptr<Widget> w); // 转移所有权
void process(std::shared_ptr<Widget> w); // 共享所有权(有开销)关键认知
unique_ptr 是默认选择,零开销;shared_ptr 用于真正需要共享所有权的场景;weak_ptr 打破循环引用。函数参数传引用或裸指针,不要无谓地传智能指针。