C++智能指针完全指南
一、智能指针概述
1.1 为什么需要智能指针
C++98的问题
// ❌ 手动内存管理,容易出错
void process() {
MyClass* obj = new MyClass();
try {
obj->doWork();
// ...复杂逻辑
delete obj; // 可能执行不到
} catch (const std::exception& e) {
delete obj; // 需要手动清理
throw;
}
}
智能指针的优势
// ✅ 自动管理,异常安全
#include <memory>
void process() {
auto obj = std::make_unique<MyClass>();
obj->doWork();
// 自动释放,无需手动delete
}
1.2 智能指针类型对比
| 类型 | 所有权 | 引用计数 | 使用场景 | C++标准 |
|---|---|---|---|---|
| unique_ptr | 独占 | 无 | 单一所有者 | C++11 |
| shared_ptr | 共享 | 有 | 多个所有者 | C++11 |
| weak_ptr | 弱引用 | 不增加 | 观察shared_ptr | C++11 |
1.3 内存管理对比
// ❌ 原始指针
MyClass* p1 = new MyClass();
MyClass* p2 = p1; // 谁负责delete?
delete p1; // p2变成悬空指针
// ✅ unique_ptr
auto p1 = std::make_unique<MyClass>();
// auto p2 = p1; // 编译错误,无法复制
// ✅ shared_ptr
auto p1 = std::make_shared<MyClass>();
auto p2 = p1; // 引用计数+1
// 自动管理内存
二、unique_ptr详解
2.1 基本用法
创建unique_ptr
#include <memory>
// 方式1:make_unique (C++14)
auto ptr1 = std::make_unique<MyClass>();
// 方式2:new表达式
auto ptr2 = std::unique_ptr<MyClass>(new MyClass());
// 方式3:自定义删除器
auto deleter = [](MyClass* p) {
std::cout << "Deleting..." << std::endl;
delete p;
};
auto ptr3 = std::unique_ptr<MyClass, decltype(deleter)>(
new MyClass(), deleter);
2.2 所有权转移
// 移动语义
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
// ✅ 移动所有权
std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
// ptr1现在为nullptr
assert(ptr1 == nullptr);
assert(ptr2 != nullptr);
// ❌ 无法复制
// auto ptr3 = ptr2; // 编译错误
2.3 使用unique_ptr管理数组
// 管理数组
auto arr = std::make_unique<int[]>(10);
// 访问元素
for (int i = 0; i < 10; ++i) {
arr[i] = i * 2;
}
// 自定义数组删除器
auto arr2 = std::unique_ptr<int[]>(new int[10]);
2.4 实际应用场景
// 工厂函数返回unique_ptr
std::unique_ptr<MyClass> createMyClass() {
return std::make_unique<MyClass>();
}
// 使用
auto obj = createMyClass();
obj->doWork();
// 容器存储unique_ptr
std::vector<std::unique_ptr<Animal>> zoo;
zoo.push_back(std::make_unique<Dog>());
zoo.push_back(std::make_unique<Cat>());
for (const auto& animal : zoo) {
animal->speak();
}
三、shared_ptr详解
3.1 基本用法
创建shared_ptr
#include <memory>
// 方式1:make_shared (推荐)
auto ptr1 = std::make_shared<MyClass>();
// 方式2:new表达式 (效率较低)
auto ptr2 = std::shared_ptr<MyClass>(new MyClass());
// 方式3:从unique_ptr创建
std::unique_ptr<MyClass> unique = std::make_unique<MyClass>();
auto ptr3 = std::move(unique);
3.2 引用计数
// 引用计数示例
auto ptr1 = std::make_shared<MyClass>();
std::cout << "use_count: " << ptr1.use_count() << std::endl; // 1
{
auto ptr2 = ptr1;
std::cout << "use_count: " << ptr1.use_count() << std::endl; // 2
std::cout << "use_count: " << ptr2.use_count() << std::endl; // 2
}
std::cout << "use_count: " << ptr1.use_count() << std::endl; // 1
3.3 自定义删除器
// 自定义删除器
auto deleter = [](MyClass* p) {
std::cout << "Custom deleter" << std::endl;
delete p;
};
auto ptr = std::shared_ptr<MyClass>(new MyClass(), deleter);
3.4 make_shared的优势
// ❌ 使用new:两次内存分配
// 1. 分配MyClass对象
// 2. 分配控制块(引用计数等)
auto ptr1 = std::shared_ptr<MyClass>(new MyClass());
// ✅ 使用make_shared:一次内存分配
// 同时分配对象和控制块
auto ptr2 = std::make_shared<MyClass>();
四、weak_ptr详解
4.1 解决循环引用
循环引用问题
// ❌ 循环引用导致内存泄漏
class Node {
public:
std::shared_ptr<Node> next;
// ...
};
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2;
node2->next = node1; // 循环引用,引用计数永远不会归零
使用weak_ptr解决
// ✅ 使用weak_ptr打破循环
class Node {
public:
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 弱引用
// ...
};
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2;
node2->prev = node1; // weak_ptr不增加引用计数
4.2 weak_ptr操作
auto shared = std::make_shared<MyClass>();
std::weak_ptr<MyClass> weak = shared;
// 检查是否过期
if (!weak.expired()) {
// 锁定获取shared_ptr
auto locked = weak.lock();
locked->doWork();
}
// 获取引用计数
std::cout << "use_count: " << weak.use_count() << std::endl;
五、最佳实践
5.1 创建智能指针
// ✅ 推荐:使用make_unique/make_shared
auto ptr = std::make_unique<MyClass>();
auto ptr2 = std::make_shared<MyClass>();
// ❌ 不推荐:直接使用new
auto ptr3 = std::unique_ptr<MyClass>(new MyClass());
5.2 函数参数
// 按值传递(增加引用计数)
void process(std::shared_ptr<MyClass> ptr);
// 按const引用传递(不增加引用计数)
void process(const std::shared_ptr<MyClass>& ptr);
// ✅ 推荐:按const引用传递,避免不必要的引用计数操作
void process(const std::shared_ptr<MyClass>& ptr) {
ptr->doWork();
}
// 对于unique_ptr,传递所有权
void takeOwnership(std::unique_ptr<MyClass> ptr);
5.3 返回值
// ✅ 返回unique_ptr:转移所有权
std::unique_ptr<MyClass> createObject() {
return std::make_unique<MyClass>();
}
// ✅ 返回shared_ptr:共享所有权
std::shared_ptr<MyClass> createShared() {
return std::make_shared<MyClass>();
}
5.4 避免混用原始指针
// ❌ 不推荐:混用原始指针和智能指针
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
MyClass* raw = ptr.get();
// raw可能在ptr销毁后失效
// ✅ 推荐:始终使用智能指针
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
auto ptr2 = ptr; // 使用智能指针
六、实际应用案例
6.1 资源管理
// 文件管理
class File {
public:
File(const std::string& path) {
file = fopen(path.c_str(), "r");
}
~File() {
if (file) {
fclose(file);
}
}
// 禁止拷贝
File(const File&) = delete;
File& operator=(const File&) = delete;
void read() {
// 读取文件
}
private:
FILE* file;
};
// 使用unique_ptr管理
auto file = std::make_unique<File>("data.txt");
file->read();
6.2 图结构
// 图节点
class GraphNode {
public:
using NodePtr = std::shared_ptr<GraphNode>;
using WeakNodePtr = std::weak_ptr<GraphNode>;
void addNeighbor(NodePtr neighbor) {
neighbors.push_back(neighbor);
}
std::vector<NodePtr> neighbors;
WeakNodePtr parent; // 避免循环引用
};
// 构建图
auto node1 = std::make_shared<GraphNode>();
auto node2 = std::make_shared<GraphNode>();
auto node3 = std::make_shared<GraphNode>();
node1->addNeighbor(node2);
node2->addNeighbor(node3);
6.3 观察者模式
// 主题
class Subject {
public:
using ObserverPtr = std::shared_ptr<Observer>;
using WeakObserverPtr = std::weak_ptr<Observer>;
void addObserver(ObserverPtr observer) {
observers.push_back(observer);
}
void notify() {
for (auto& weakObs : observers) {
if (auto obs = weakObs.lock()) {
obs->update();
}
}
}
private:
std::vector<WeakObserverPtr> observers;
};
七、性能考虑
7.1 性能对比
| 操作 | unique_ptr | shared_ptr | 原始指针 |
|---|---|---|---|
| 创建 | 低开销 | 1次分配 | 零开销 |
| 复制 | 禁止 | 原子操作 | 零开销 |
| 移动 | 零开销 | 零开销 | N/A |
| 访问 | 零开销 | 一次间接 | 零开销 |
| 销毁 | 零开销 | 原子操作 | 零开销 |
7.2 优化建议
// ✅ 优先使用unique_ptr
auto ptr = std::make_unique<MyClass>();
// ✅ 需要共享时使用shared_ptr
auto shared = std::make_shared<MyClass>();
// ✅ 使用const引用传递
void func(const std::shared_ptr<MyClass>& ptr);
// ⚠️ 避免过度使用shared_ptr
// 如果不需要共享所有权,使用unique_ptr
八、常见陷阱
8.1 this指针问题
// ❌ 错误:从this创建shared_ptr
class MyClass {
public:
void registerSelf() {
// 危险:可能创建多个独立的shared_ptr
registry.push_back(std::shared_ptr<MyClass>(this));
}
};
// ✅ 正确:使用enable_shared_from_this
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
void registerSelf() {
registry.push_back(shared_from_this());
}
};
8.2 数组问题
// ❌ 错误:shared_ptr默认使用delete,不是delete[]
auto ptr = std::shared_ptr<int[]>(new int[10]);
// ✅ 正确:指定删除器或使用unique_ptr
auto ptr1 = std::unique_ptr<int[]>(new int[10]);
auto ptr2 = std::shared_ptr<int>(new int[10], std::default_delete<int[]>());
8.3 交叉引用
// ❌ 交叉引用问题
class A {
std::shared_ptr<B> b;
};
class B {
std::shared_ptr<A> a;
};
// ✅ 使用weak_ptr打破交叉引用
class A {
std::shared_ptr<B> b;
};
class B {
std::weak_ptr<A> a;
};
九、总结
9.1 选择指南
需要智能指针?
↓
所有权转移?
├─ 是 → unique_ptr
└─ 否 → 需要共享?
├─ 是 → shared_ptr
└─ 否 → 原始指针或值
9.2 核心要点
| 要点 | 说明 |
|---|---|
| 默认选择 | unique_ptr |
| 共享所有权 | shared_ptr |
| 观察者 | weak_ptr |
| 创建方式 | make_unique/make_shared |
| 禁止拷贝 | unique_ptr |
| 引用计数 | shared_ptr |
| 循环引用 | weak_ptr解决 |
9.3 最佳实践
- ✅ 优先使用
make_unique和make_shared - ✅ 使用
unique_ptr作为默认选择 - ✅ 需要
shared_ptr时明确语义 - ✅ 使用
weak_ptr解决循环引用 - ✅ 使用
const引用传递shared_ptr - ❌ 避免混用原始指针
- ❌ 避免不必要的使用
shared_ptr - ❌ 不要用
this创建shared_ptr