智能指针详解
00:00
unique_ptr、shared_ptr与weak_ptr
概述
智能指针是 C++ 管理动态内存的核心工具,通过 RAII 机制自动释放资源,消除了手动 new/delete 带来的内存泄漏和悬空指针风险。C++ 标准库提供了三种智能指针:std::unique_ptr 实现独占所有权,std::shared_ptr 实现共享所有权,std::weak_ptr 解决共享所有权中的循环引用问题。
智能指针的设计哲学是:通过类型系统表达所有权语义,让编译器帮助开发者正确管理资源。
基础概念
三种智能指针对比
| 指针 | 所有权 | 引用计数 | 开销 | 适用场景 |
|---|---|---|---|---|
unique_ptr | 独占 | 无 | 几乎为零 | 默认选择 |
shared_ptr | 共享 | 有(原子操作) | 较高 | 需要共享所有权 |
weak_ptr | 无(观察者) | 不增加 | 低 | 打破循环引用 |
所有权原则
- 默认使用
unique_ptr,只在需要共享时使用shared_ptr - 所有权应尽量明确,避免模糊的共享关系
- 使用
make_unique和make_shared创建智能指针,避免裸 new
快速上手
unique_ptr 独占所有权
#include <memory>
#include <iostream>
class Widget {
public:
Widget() { std::cout << "Widget 构造" << std::endl; }
~Widget() { std::cout << "Widget 析构" << std::endl; }
void use() { std::cout << "Widget 使用" << std::endl; }
};
int main() {
// 创建 unique_ptr
auto p = std::make_unique<Widget>(); // 推荐:异常安全
// 使用
p->use();
std::cout << "地址: " << p.get() << std::endl;
// 所有权转移(移动语义)
auto q = std::move(p); // p 变为 nullptr,q 获得所有权
if (!p) std::cout << "p 已为空" << std::endl;
// 离开作用域时 q 自动销毁 Widget
return 0;
}
shared_ptr 共享所有权
#include <memory>
#include <iostream>
int main() {
auto p1 = std::make_shared<int>(42);
auto p2 = p1; // 共享所有权,引用计数 +1
std::cout << "值: " << *p1 << std::endl; // 42
std::cout << "引用计数: " << p1.use_count() << std::endl; // 2
p1.reset(); // p1 释放引用,引用计数 -1
std::cout << "p2 引用计数: " << p2.use_count() << std::endl; // 1
// p2 仍有效,对象未销毁
return 0;
}
weak_ptr 观察者
#include <memory>
#include <iostream>
int main() {
auto sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp; // 不增加引用计数
std::cout << "引用计数: " << sp.use_count() << std::endl; // 1
// 安全访问
if (auto locked = wp.lock()) {
std::cout << "值: " << *locked << std::endl; // 42
}
sp.reset(); // 销毁对象
std::cout << "是否过期: " << wp.expired() << std::endl; // true
return 0;
}
详细用法
unique_ptr 与数组
#include <memory>
// 管理动态数组
auto arr = std::make_unique<int[]>(100); // C++14
arr[0] = 42;
arr[1] = 100;
// 注意:unique_ptr<T[]> 不提供 operator* 和 operator->
// 只提供 operator[]
// 更推荐使用 std::vector 或 std::array
自定义删除器
#include <memory>
#include <cstdio>
// 使用自定义删除器管理 FILE*
auto file_closer = [](FILE* f) {
if (f) {
std::fclose(f);
std::cout << "文件已关闭" << std::endl;
}
};
using unique_file = std::unique_ptr<FILE, decltype(file_closer)>;
unique_file openFile(const char* path, const char* mode) {
FILE* f = std::fopen(path, mode);
if (!f) throw std::runtime_error("无法打开文件");
return unique_file(f, file_closer);
}
// 管理 Windows HANDLE
#ifdef _WIN32
auto handle_closer = [](void* h) {
if (h != INVALID_HANDLE_VALUE) CloseHandle(h);
};
std::unique_ptr<void, decltype(handle_closer)> win_handle(h, handle_closer);
#endif
shared_ptr 与别名构造
#include <memory>
#include <string>
struct Config {
std::string host;
int port;
std::string database;
};
// shared_ptr 的别名构造:指向成员但共享整个对象的生命周期
std::shared_ptr<std::string> getHost(std::shared_ptr<Config> config) {
// 创建指向 config->host 的 shared_ptr
// 但引用计数与 config 共享
return std::shared_ptr<std::string>(config, &config->host);
}
// 使用
auto cfg = std::make_shared<Config>(Config{"localhost", 3306, "mydb"});
auto host = getHost(cfg);
// cfg 和 host 共享引用计数,Config 不会被提前销毁
工厂函数返回智能指针
#include <memory>
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
};
class Circle : public Shape {
double radius_;
public:
explicit Circle(double r) : radius_(r) {}
double area() const override { return 3.14159 * radius_ * radius_; }
};
class Rectangle : public Shape {
double width_, height_;
public:
Rectangle(double w, double h) : width_(w), height_(h) {}
double area() const override { return width_ * height_; }
};
// 工厂函数返回 unique_ptr,所有权清晰
std::unique_ptr<Shape> createShape(const std::string& type, double a, double b = 0) {
if (type == "circle") return std::make_unique<Circle>(a);
if (type == "rectangle") return std::make_unique<Rectangle>(a, b);
throw std::invalid_argument("未知形状");
}
常见场景
Pimpl 惯用法
// widget.h
class Widget {
public:
Widget();
~Widget();
void doWork();
private:
struct Impl;
std::unique_ptr<Impl> impl_; // 隐藏实现细节
};
// widget.cpp
struct Widget::Impl {
std::vector<int> data;
void process() { /* 复杂实现 */ }
};
Widget::Widget() : impl_(std::make_unique<Impl>()) {}
Widget::~Widget() = default; // 必须在实现文件中定义,因为 Impl 在此处完整
void Widget::doWork() { impl_->process(); }
异步回调中的安全引用
#include <memory>
class Service : public std::enable_shared_from_this<Service> {
public:
void startAsync() {
// 使用 shared_from_this() 确保回调执行时对象仍存活
auto self = shared_from_this();
std::thread([self]() {
self->processResult(); // self 保持对象存活
}).detach();
}
void processResult() { /* 处理结果 */ }
};
注意事项
- 优先使用
make_unique和make_shared创建智能指针,避免裸 new 和潜在的内存泄漏 unique_ptr不能拷贝,只能移动;如果需要拷贝语义,应考虑是否真的需要共享所有权shared_ptr的引用计数是原子操作,有一定性能开销,不应在性能关键路径上频繁拷贝- 避免从裸指针创建多个
shared_ptr,这会导致多个独立的引用计数,对象会被多次删除 weak_ptr::lock()返回后应立即使用,不要存储后再使用,因为对象可能随时被销毁- 不要在构造函数中调用
shared_from_this(),此时对象尚未被shared_ptr管理
进阶用法
shared_ptr 的线程安全性
#include <memory>
#include <mutex>
// shared_ptr 的线程安全规则:
// 1. 引用计数本身是线程安全的(原子操作)
// 2. 访问指向的对象不是线程安全的,需要额外同步
// 3. 不同的 shared_ptr 实例可以被不同线程同时读取
class ThreadSafeData {
std::shared_ptr<const std::vector<int>> data_;
std::mutex mtx_;
public:
std::shared_ptr<const std::vector<int>> get() {
std::lock_guard<std::mutex> lock(mtx_);
return data_; // 返回副本,线程安全
}
void update(std::shared_ptr<const std::vector<int>> newData) {
std::lock_guard<std::mutex> lock(mtx_);
data_ = std::move(newData); // 原子替换
}
};
intrusive_ptr 模式
// 当 shared_ptr 的控制块开销不可接受时,可以使用侵入式引用计数
class RefCounted {
mutable std::atomic<int> refCount_{0};
public:
void addRef() const { refCount_.fetch_add(1, std::memory_order_relaxed); }
void release() const {
if (refCount_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete this;
}
}
protected:
virtual ~RefCounted() = default;
};
// 侵入式智能指针(简化版)
template<typename T>
class IntrusivePtr {
T* ptr_;
public:
explicit IntrusivePtr(T* p = nullptr) : ptr_(p) {
if (ptr_) ptr_->addRef();
}
~IntrusivePtr() { if (ptr_) ptr_->release(); }
IntrusivePtr(const IntrusivePtr& other) : ptr_(other.ptr_) {
if (ptr_) ptr_->addRef();
}
T* operator->() const { return ptr_; }
T& operator*() const { return *ptr_; }
};