C++: 智能指针进阶
最后更新:2026-08-26
第45课我们学了移动语义。
现在,我们要深入智能指针——现代C++内存管理的核心工具。
用了智能指针,就再也不用担心内存泄漏了。
1. 智能指针概述
(1) 1.1 什么是智能指针?
智能指针是管理动态内存的模板类,自动释放内存(RAII)。
三种智能指针:
| 智能指针 | 功能 | 适用场景 |
|---|---|---|
unique_ptr |
独占所有权 | 唯一拥有对象 |
shared_ptr |
共享所有权 | 多个地方需要访问同一对象 |
weak_ptr |
弱引用 | 解决循环引用 |
2. unique_ptr
(1) 2.1 基本用法
unique_ptr 是独占所有权的智能指针,不能拷贝,只能移动。
示例:unique_ptr基本用法(难度⭐)
▶ 示例 2:代码示例(难度⭐)
CPP
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> p1(new int(42));
std::cout << *p1 << std::endl; // 输出:42
// std::unique_ptr<int> p2 = p1; // ❌ 错误!不能拷贝
std::unique_ptr<int> p2 = std::move(p1); // ✅ 可以移动
std::cout << *p2 << std::endl; // 输出:42
return 0;
} // p2自动释放内存
输出:
TEXT
📖 仅展示
(程序输出)
(2) 2.2 自定义删除器
示例:用unique_ptr管理文件(难度⭐⭐)
CPP
#include <iostream>
#include <memory>
#include <cstdio>
// 自定义删除器:关闭文件
struct FileDeleter {
void operator()(FILE* fp) const {
if (fp) {
fclose(fp);
std::cout << "文件已关闭" << std::endl;
}
}
};
int main() {
std::unique_ptr<FILE, FileDeleter> file(fopen("test.txt", "w"));
// 不需要手动fclose,unique_ptr自动调用FileDeleter
return 0;
}
3. shared_ptr
(1) 3.1 基本用法
shared_ptr 是共享所有权的智能指针,用引用计数管理内存。
示例:shared_ptr基本用法(难度⭐)
CPP
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> p1 = std::make_shared<int>(42);
std::cout << "引用计数:" << p1.use_count() << std::endl; // 1
{
std::shared_ptr<int> p2 = p1; // 拷贝,引用计数+1
std::cout << "引用计数:" << p1.use_count() << std::endl; // 2
} // p2销毁,引用计数-1
std::cout << "引用计数:" << p1.use_count() << std::endl; // 1
return 0;
}
(2) 3.2 make_shared vs new
推荐用法: 用 std::make_shared 而不是 new。
| 对比 | new |
make_shared |
|---|---|---|
| 异常安全 | 可能泄漏 | 安全 |
| 性能 | 两次分配 | 一次分配 |
| 代码简洁 | 冗长 | 简洁 |
CPP
// 推荐
auto p1 = std::make_shared<int>(42);
// 不推荐
std::shared_ptr<int> p2(new int(42));
4. weak_ptr
(1) 4.1 为什么需要weak_ptr?
问题: shared_ptr 可能导致循环引用,内存泄漏。
示例:循环引用(难度⭐⭐⭐)
CPP
#include <iostream>
#include <memory>
struct Node {
std::shared_ptr<Node> next; // Circular reference!
~Node() { std::cout << "Node destroyed" << std::endl; }
};
int main() {
auto n1 = std::make_shared<Node>();
auto n2 = std::make_shared<Node>();
n1->next = n2; // Circular reference
n2->next = n1;
return 0;
} // ❌ n1 and n2 will not be released (memory leak)
(2) 4.2 用weak_ptr打破循环引用
解决方案: 把其中一个 shared_ptr 改为 weak_ptr。
CPP
struct Node {
std::weak_ptr<Node> next; // Use weak_ptr, does not increase reference count
~Node() { std::cout << "Node destroyed" << std::endl; }
};
int main() {
auto n1 = std::make_shared<Node>();
auto n2 = std::make_shared<Node>();
n1->next = n2; // Does not increase reference count
n2->next = n1;
return 0;
} // ✅ Released correctly
5. 智能指针选择指南
(1) 5.1 如何选择?
| 场景 | 推荐 |
|---|---|
| 唯一拥有 | unique_ptr |
| 共享拥有 | shared_ptr |
| 观察者 | weak_ptr 或原始指针 |
| 数组 | unique_ptr<T> |
(2) 5.2 不要做的事
| 错误 | 说明 |
|---|---|
| 不要用多个原始指针初始化shared_ptr | 会导致重复释放 |
| 不要混用原始指针和智能指针 | 破坏RAII |
| 不要手动调用delete | 让智能指针管理 |
6. 实战:智能指针管理资源
▶ 示例 1:用智能指针管理数据库连接(难度⭐⭐⭐)
CPP
#include <iostream>
#include <memory>
// 模拟数据库连接
class DatabaseConnection {
public:
DatabaseConnection() {
std::cout << "连接数据库" << std::endl;
}
~DatabaseConnection() {
std::cout << "断开连接" << std::endl;
}
void query(const std::string& sql) {
std::cout << "执行SQL:" << sql << std::endl;
}
};
int main() {
// 用unique_ptr管理独占资源
std::unique_ptrDatabaseConnection conn(new DatabaseConnection());
conn->query("SELECT * FROM users");
return 0;
} // 自动断开连接
输出:
TEXT
📖 仅展示
连接数据库
断开连接
执行SQL:
❓ 常见问题
Q unique_ptr和shared_ptr哪个快?
A
unique_ptr 更快(无引用计数开销),优先用 unique_ptr。Q 什么时候用原始指针?
A - 不拥有所有权时(观察者) - 与C库交互时 - 性能极度敏感时(但通常不需要)
Q:shared_ptr线程安全吗? A:- 引用计数的修改是线程安全的 - 但指向的对象不是线程安全的(需要互斥量保护)
▶ 示例 3:shared_ptr共享所有权(难度⭐)
CPP
#include <iostream>
#include <memory>
class Resource {
public:
Resource() { std::cout << "构造" << std::endl; }
~Resource() { std::cout << "析构" << std::endl; }
};
int main() {
{
std::shared_ptr<Resource> p1 = std::make_shared<Resource>();
std::shared_ptr<Resource> p2 = p1; // 共享所有权
std::cout << "引用计数:" << p1.use_count() << std::endl;
}
// 离开作用域,自动析构
std::cout << "程序结束" << std::endl;
return 0;
}
输出:
TEXT
📖 仅展示
构造
析构
引用计数:
程序结束
💡 提示:
shared_ptr 共享所有权,引用计数归零时自动释放。用 make_shared 创建更安全。
| 知识点 | 要点 |
|---|---|
| unique_ptr | 独占所有权,不能拷贝 |
| shared_ptr | 共享所有权,引用计数 |
| weak_ptr | 弱引用,打破循环引用 |
| make_shared | 推荐用法,异常安全 |
| 选择原则 | 优先unique_ptr,需要时再用shared_ptr |
📖 小节
- shared_ptr:共享所有权,引用计数管理
- unique_ptr:独占所有权,不可复制
- weak_ptr:弱引用,不增加引用计数
- make_shared/make_unique:推荐创建方式
📝 作业
-
**基础题 (Difficulty ⭐):用 unique_ptr 管理一个动态分配的 int,用 make_unique 创建。尝试拷贝 unique_ptr(应该编译失败),改用 move。
-
**进阶题 (Difficulty ⭐⭐):用 shared_ptr 实现多个对象共享同一个资源。创建两个 shared_ptr 指向同一个对象,输出 use_count() 观察引用计数变化。
-
**挑战题 (Difficulty ⭐⭐⭐):用 weak_ptr 解决 shared_ptr 循环引用问题。创建 A 类和 B 类互相持有 shared_ptr,观察内存泄漏。改用 weak_ptr 后验证正确释放。
- unique_ptr 独占所有权,不可复制可移动
- shared_ptr 共享所有权,引用计数管理
- weak_ptr 配合 shared_ptr 避免循环引用
- make_unique/make_shared 异常安全的方式创建
- 自定义删除器处理特殊资源释放
下一课:模板元编程入门(#47)