现代C++——智能指针
📝 智能指针摘要 本章系统介绍了现代C++中三种核心智能指针:unique_ptr、shared_ptr和weak_ptr。unique_ptr提供独占所有权,轻量高效,适合单一所有者场景;shared_ptr通过引用计数实现共享所有权,但需注意循环引用问题;weak_ptr作为弱引用可安全解决循环依赖。智能指针基于RAII原则,自动管理资源生命周期,确保异常安全,是现代C++内存管理的首选方案
第二章:智能指针
📚 章节概述
智能指针是现代C++中最重要的特性之一,它们通过RAII(Resource Acquisition Is Initialization)原则自动管理内存,避免了传统裸指针可能导致的内存泄漏、野指针等问题。本章将深入介绍三种主要的智能指针:unique_ptr、shared_ptr和weak_ptr。
🎯 学习目标
学完本章后,您将能够:
- 理解智能指针的设计原理和RAII概念
- 掌握
unique_ptr的使用场景和最佳实践 - 理解
shared_ptr的引用计数机制 - 使用
weak_ptr解决循环引用问题 - 掌握自定义删除器的用法
- 了解智能指针与STL容器的配合使用
- 遵循智能指针的最佳实践原则
1. 智能指针基础概念
📖 理论讲解
智能指针是封装了裸指针的类模板,通过RAII原则自动管理动态分配的内存。当智能指针离开作用域时,会自动释放其管理的内存。
💡 核心概念
- RAII原则:Resource Acquisition Is Initialization,资源获取即初始化
- 自动内存管理:无需手动调用delete
- 异常安全:即使发生异常也能正确释放资源
- 所有权语义:明确表达资源的所有权关系
🔍 传统指针的问题
// 传统指针的问题示例
void problematic_function() {
int* ptr = new int(42);
// 如果这里抛出异常,内存就泄漏了
if (some_condition()) {
throw std::runtime_error("Error occurred");
}
delete ptr; // 可能永远执行不到
}
// 智能指针的解决方案
void safe_function() {
std::unique_ptr<int> ptr = std::make_unique<int>(42);
// 即使抛出异常,ptr也会自动释放内存
if (some_condition()) {
throw std::runtime_error("Error occurred");
}
// 无需手动delete
}
2. unique_ptr - 独占所有权智能指针
📖 理论讲解
unique_ptr提供对动态分配对象的独占所有权。它不能被拷贝,只能被移动,确保任何时候只有一个unique_ptr指向特定的对象。
💡 核心特性
- 独占所有权:同一时间只能有一个
unique_ptr拥有对象 - 移动语义:支持移动但不支持拷贝
- 零开销:几乎没有性能开销
- 自定义删除器:可以自定义资源释放方式
🔍 基本用法
#include <memory>
// 1. 创建unique_ptr
std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
std::unique_ptr<int> ptr2(new int(100)); // 也可以,但不推荐
// 2. 访问对象
std::cout << *ptr1 << std::endl; // 解引用
if (ptr1) { // 检查是否为空
std::cout << "ptr1 is not null" << std::endl;
}
// 3. 移动所有权
std::unique_ptr<int> ptr3 = std::move(ptr1);
// 现在ptr1为空,ptr3拥有对象
// 4. 重置和释放
ptr3.reset(); // 删除对象,ptr3变为空
ptr3.reset(new int(200)); // 删除旧对象,指向新对象
// 5. 释放所有权(不删除对象)
int* raw_ptr = ptr3.release();
delete raw_ptr; // 需要手动删除
🚀 高级用法
// 1. 数组的unique_ptr
std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
arr[0] = 42; // 支持数组索引操作
// 2. 自定义删除器
auto custom_deleter = [](FILE* f) {
if (f) {
std::cout << "Closing file" << std::endl;
fclose(f);
}
};
std::unique_ptr<FILE, decltype(custom_deleter)> file_ptr(
fopen("test.txt", "w"), custom_deleter);
⚠️ 注意事项
- 不能直接赋值拷贝,只能移动
- 使用
make_unique比直接使用new更安全 - 可以存储在容器中(通过移动)
🎯 使用场景
- 独占资源管理:文件句柄、网络连接等
- 工厂函数返回值:返回动态创建的对象
- 容器元素:存储多态对象
- PIMPL惯用法:隐藏实现细节
3. shared_ptr - 共享所有权智能指针
📖 理论讲解
shared_ptr使用引用计数来管理对象的生命周期。多个shared_ptr可以指向同一个对象,当最后一个shared_ptr销毁时,对象才会被删除。
💡 核心特性
- 共享所有权:多个
shared_ptr可以共享同一对象 - 引用计数:自动跟踪指向对象的
shared_ptr数量 - 线程安全:引用计数操作是线程安全的
- 支持拷贝:可以像普通对象一样拷贝和赋值
🔍 基本用法
// 1. 创建shared_ptr
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2(new int(100)); // 不推荐
// 2. 共享所有权
std::shared_ptr<int> ptr3 = ptr1; // 引用计数变为2
std::cout << "Use count: " << ptr1.use_count() << std::endl; // 输出: 2
// 3. 检查引用计数
if (ptr1.use_count() == 1) {
std::cout << "Only one reference" << std::endl;
}
// 4. 重置
ptr3.reset(); // 引用计数减1
ptr1.reset(); // 引用计数变为0,对象被删除
🚀 高级特性
// 1. 弱引用转换
std::weak_ptr<int> weak = ptr1;
if (auto locked = weak.lock()) { // 尝试获取shared_ptr
std::cout << *locked << std::endl;
}
// 2. 别名构造
struct Node {
std::shared_ptr<Node> child;
int value;
};
std::shared_ptr<Node> node = std::make_shared<Node>();
std::shared_ptr<int> value_ptr(node, &node->value); // 指向value但共享node的生命周期
// 3. 自定义删除器
std::shared_ptr<FILE> file(fopen("test.txt", "r"),
[](FILE* f) { if(f) fclose(f); });
⚠️ 注意事项
- 有一定的内存和性能开销
- 避免循环引用(使用
weak_ptr解决) - 优先使用
make_shared而不是new
🎯 使用场景
- 共享资源:多个对象需要访问同一资源
- 缓存系统:多个地方引用同一对象
- 观察者模式:多个观察者共享被观察对象
- 图结构:节点之间的复杂引用关系
4. weak_ptr - 弱引用智能指针
📖 理论讲解
weak_ptr提供对shared_ptr管理对象的弱引用,不影响对象的生命周期。主要用于解决shared_ptr的循环引用问题。
💡 核心特性
- 不影响引用计数:不会增加
shared_ptr的引用计数 - 避免循环引用:打破
shared_ptr循环引用 - 安全检查:可以检查对象是否还存在
- 临时访问:可以临时获得
shared_ptr来访问对象
🔍 基本用法
// 1. 创建weak_ptr
std::shared_ptr<int> shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared;
// 2. 检查对象是否存在
if (!weak.expired()) {
std::cout << "Object still exists" << std::endl;
}
// 3. 安全访问对象
if (auto locked = weak.lock()) {
std::cout << *locked << std::endl;
} else {
std::cout << "Object has been destroyed" << std::endl;
}
// 4. 获取引用计数
std::cout << "Use count: " << weak.use_count() << std::endl;
🚀 解决循环引用
// 循环引用问题示例
class Node {
public:
std::shared_ptr<Node> child;
std::weak_ptr<Node> parent; // 使用weak_ptr避免循环引用
std::string name;
Node(const std::string& n) : name(n) {
std::cout << "Node " << name << " created" << std::endl;
}
~Node() {
std::cout << "Node " << name << " destroyed" << std::endl;
}
void set_child(std::shared_ptr<Node> c) {
child = c;
if (c) {
c->parent = shared_from_this(); // 需要继承enable_shared_from_this
}
}
};
// 正确的使用方式
auto root = std::make_shared<Node>("Root");
auto child = std::make_shared<Node>("Child");
root->set_child(child);
// 当root和child离开作用域时,都会被正确销毁
🎯 使用场景
- 父子关系:子对象持有父对象的弱引用
- 观察者模式:观察者持有被观察对象的弱引用
- 缓存系统:缓存对象的弱引用,允许对象被自动清理
- 回调函数:避免回调中的循环引用
5. 自定义删除器
📖 理论讲解
智能指针允许自定义删除器来处理特殊的资源释放需求,如文件句柄、网络连接等非内存资源。
💡 核心概念
- 资源多样性:不仅仅管理内存,还可以管理其他资源
- RAII扩展:将RAII原则应用到各种资源
- 类型安全:编译时确定删除器类型
🔍 使用示例
// 1. 函数指针删除器
void close_file(FILE* f) {
if (f) {
std::cout << "Closing file" << std::endl;
fclose(f);
}
}
std::unique_ptr<FILE, void(*)(FILE*)> file_ptr(fopen("test.txt", "w"), close_file);
// 2. Lambda删除器
auto file_closer = [](FILE* f) {
if (f) {
std::cout << "Lambda closing file" << std::endl;
fclose(f);
}
};
std::unique_ptr<FILE, decltype(file_closer)> file_ptr2(
fopen("test2.txt", "w"), file_closer);
// 3. 函数对象删除器
struct ArrayDeleter {
void operator()(int* p) {
std::cout << "Deleting array" << std::endl;
delete[] p;
}
};
std::unique_ptr<int, ArrayDeleter> arr_ptr(new int[10]);
// 4. shared_ptr的删除器(类型擦除)
std::shared_ptr<FILE> shared_file(
fopen("test3.txt", "w"),
[](FILE* f) { if(f) fclose(f); }
);
🚀 高级应用
// 管理系统资源
class SocketDeleter {
public:
void operator()(int* socket_fd) {
if (socket_fd && *socket_fd != -1) {
std::cout << "Closing socket " << *socket_fd << std::endl;
close(*socket_fd);
}
delete socket_fd;
}
};
std::unique_ptr<int, SocketDeleter> socket_ptr(new int(socket(AF_INET, SOCK_STREAM, 0)));
// 管理Windows句柄(示例)
#ifdef _WIN32
struct HandleDeleter {
void operator()(HANDLE handle) {
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
}
}
};
std::unique_ptr<void, HandleDeleter> handle_ptr(CreateFile(...));
#endif
🎯 最佳实践
- Lambda优先:对于简单的删除逻辑,使用Lambda
- 函数对象复用:复杂逻辑可以创建专门的删除器类
- 异常安全:删除器不应该抛出异常
- null检查:删除器应该检查指针是否为null
6. 智能指针与STL容器
📖 理论讲解
智能指针与STL容器的结合使用是现代C++中管理动态对象集合的标准方式。
🔍 容器中的智能指针
// 1. unique_ptr容器
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(3.0, 4.0));
// 遍历多态对象
for (const auto& shape : shapes) {
shape->draw(); // 多态调用
}
// 2. shared_ptr容器
std::vector<std::shared_ptr<Resource>> resources;
auto res = std::make_shared<Resource>("resource1");
resources.push_back(res);
resources.push_back(res); // 同一资源可以被多次引用
// 3. 容器操作
// 移动元素
auto moved_shape = std::move(shapes[0]);
shapes.erase(shapes.begin());
// 查找和操作
auto it = std::find_if(shapes.begin(), shapes.end(),
[](const std::unique_ptr<Shape>& s) {
return s->area() > 10.0;
});
🚀 高级模式
// 1. 工厂模式与容器
class ShapeFactory {
public:
static std::unique_ptr<Shape> createShape(const std::string& type) {
if (type == "circle") {
return std::make_unique<Circle>(1.0);
} else if (type == "rectangle") {
return std::make_unique<Rectangle>(1.0, 1.0);
}
return nullptr;
}
};
std::vector<std::unique_ptr<Shape>> shapes;
std::vector<std::string> types = {"circle", "rectangle", "circle"};
for (const auto& type : types) {
if (auto shape = ShapeFactory::createShape(type)) {
shapes.push_back(std::move(shape));
}
}
// 2. 缓存模式
class ResourceCache {
private:
std::unordered_map<std::string, std::weak_ptr<Resource>> cache_;
public:
std::shared_ptr<Resource> getResource(const std::string& name) {
auto it = cache_.find(name);
if (it != cache_.end()) {
if (auto locked = it->second.lock()) {
return locked; // 返回缓存的资源
} else {
cache_.erase(it); // 清理过期的弱引用
}
}
// 创建新资源
auto resource = std::make_shared<Resource>(name);
cache_[name] = resource;
return resource;
}
};
7. 智能指针最佳实践
🎯 选择指南
| 场景 | 推荐指针 | 原因 |
|---|---|---|
| 独占资源 | unique_ptr |
性能最好,语义清晰 |
| 共享资源 | shared_ptr |
支持多重所有权 |
| 避免循环引用 | weak_ptr |
不影响生命周期 |
| 管理数组 | unique_ptr<T[]> |
自动调用delete[] |
| 函数返回值 | unique_ptr |
明确所有权转移 |
| 函数参数 | 原始指针或引用 | 不转移所有权 |
🚀 编程指南
// 1. 优先使用make_unique和make_shared
auto ptr1 = std::make_unique<MyClass>(args); // 推荐
auto ptr2 = std::unique_ptr<MyClass>(new MyClass(args)); // 不推荐
auto sptr1 = std::make_shared<MyClass>(args); // 推荐
auto sptr2 = std::shared_ptr<MyClass>(new MyClass(args)); // 不推荐
// 2. 函数参数设计
void process_object(const MyClass& obj); // 不转移所有权,使用引用
void process_object(MyClass* obj); // 可能为null,使用指针
std::unique_ptr<MyClass> create_object(); // 转移所有权,返回unique_ptr
// 3. 避免从裸指针创建多个智能指针
MyClass* raw_ptr = new MyClass();
auto ptr1 = std::shared_ptr<MyClass>(raw_ptr); // 危险!
auto ptr2 = std::shared_ptr<MyClass>(raw_ptr); // 会导致双重删除
// 正确做法
auto ptr1 = std::make_shared<MyClass>();
auto ptr2 = ptr1; // 安全的共享
// 4. 使用enable_shared_from_this
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
void register_callback() {
callback_manager.register_callback(shared_from_this());
}
};
⚠️ 常见陷阱
- 双重删除:从同一个裸指针创建多个智能指针
- 循环引用:shared_ptr相互引用导致内存泄漏
- 悬挂引用:weak_ptr.lock()后不检查返回值
- 过度使用shared_ptr:能用unique_ptr就不要用shared_ptr
🧪 实践练习
练习1:设计一个简单的文件管理器
class FileManager {
public:
// 使用适当的智能指针设计以下接口:
// 1. 打开文件(独占访问)
// 2. 共享只读文件
// 3. 管理文件缓存
};
练习2:实现观察者模式
class Subject; // 前向声明
class Observer {
public:
// 使用weak_ptr避免循环引用
virtual void update(/* 参数 */) = 0;
};
class Subject {
// 管理观察者列表,避免循环引用
};
练习3:设计图数据结构
class GraphNode {
public:
// 如何设计节点之间的连接关系?
// 考虑:父子关系、邻接关系、避免循环引用
};
📝 本章小结
智能指针是现代C++内存管理的核心工具:
- unique_ptr:独占所有权,性能最优,适合大多数场景
- shared_ptr:共享所有权,支持多重引用,适合共享资源
- weak_ptr:弱引用,解决循环引用,适合观察者模式
🎯 选择原则
- 默认选择unique_ptr:除非确实需要共享所有权
- 需要共享时使用shared_ptr:多个对象需要访问同一资源
- 使用weak_ptr打破循环:避免shared_ptr循环引用
- 自定义删除器管理特殊资源:文件、socket、句柄等
🚀 进阶提示
- 结合移动语义提高性能
- 理解enable_shared_from_this的用法
- 掌握智能指针的类型转换
- 学习智能指针与异常安全的关系
智能指针不仅解决了内存管理问题,更重要的是让代码表达了清晰的所有权语义,这是现代C++设计的重要理念。
🔗 相关资源
更多推荐



所有评论(0)