C++ 智能指针深度解析
智能指针是 C++11 引入的重要特性,用于自动管理动态分配的内存,避免内存泄漏和悬空指针问题。
智能指针概述
为什么需要智能指针?
- 自动释放内存,避免内存泄漏
- 防止悬空指针和双重释放
- 提供异常安全的内存管理
- 明确表达所有权语义
RAII 原则:
- 资源获取即初始化(Resource Acquisition Is Initialization)
- 资源在构造函数中获取,在析构函数中释放
- 利用对象生命周期管理资源
unique_ptr
基本用法:
cpp#include <memory> // 创建 unique_ptr std::unique_ptr<int> ptr1(new int(42)); std::unique_ptr<int> ptr2 = std::make_unique<int>(42); // C++14 // 访问对象 *ptr1 = 100; std::cout << *ptr1 << std::endl; // 重置 ptr1.reset(); // 释放内存 ptr1.reset(new int(200)); // 重新分配 // 释放所有权 int* rawPtr = ptr1.release(); // ptr1 不再管理内存 delete rawPtr; // 手动删除 // 检查是否为空 if (ptr1) { std::cout << "ptr1 is not empty" << std::endl; }
移动语义:
cpp// unique_ptr 只能移动,不能拷贝 std::unique_ptr<int> ptr1 = std::make_unique<int>(42); std::unique_ptr<int> ptr2 = std::move(ptr1); // 移动构造 // ptr1 现在为空 if (!ptr1) { std::cout << "ptr1 is empty after move" << std::endl; } // 移动赋值 std::unique_ptr<int> ptr3; ptr3 = std::move(ptr2); // ptr2 变为空
自定义删除器:
cpp// 数组删除器 struct ArrayDeleter { void operator()(int* p) const { delete[] p; } }; std::unique_ptr<int, ArrayDeleter> arr(new int[10]); // 使用 lambda 删除器 auto deleter = [](FILE* f) { fclose(f); }; std::unique_ptr<FILE, decltype(deleter)> file(fopen("test.txt", "w"), deleter); // 使用默认数组删除器 std::unique_ptr<int[]> arr2(new int[10]); arr2[0] = 1; arr2[1] = 2;
在容器中使用:
cppstd::vector<std::unique_ptr<int>> vec; // 使用 make_unique vec.push_back(std::make_unique<int>(1)); vec.push_back(std::make_unique<int>(2)); // 使用 emplace_back vec.emplace_back(std::make_unique<int>(3)); // 遍历 for (const auto& ptr : vec) { std::cout << *ptr << std::endl; }
shared_ptr
基本用法:
cpp// 创建 shared_ptr std::shared_ptr<int> ptr1(new int(42)); std::shared_ptr<int> ptr2 = std::make_shared<int>(42); // 推荐 // 拷贝构造 std::shared_ptr<int> ptr3 = ptr1; // 引用计数 +1 // 赋值 std::shared_ptr<int> ptr4; ptr4 = ptr1; // 引用计数 +1 // 查看引用计数 std::cout << "use_count: " << ptr1.use_count() << std::endl; // 重置 ptr1.reset(); // 引用计数 -1
引用计数机制:
cppclass MyClass { public: MyClass() { std::cout << "Constructor" << std::endl; } ~MyClass() { std::cout << "Destructor" << std::endl; } }; { std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(); std::cout << "Count: " << ptr1.use_count() << std::endl; // 1 { std::shared_ptr<MyClass> ptr2 = ptr1; std::cout << "Count: " << ptr1.use_count() << std::endl; // 2 } std::cout << "Count: " << ptr1.use_count() << std::endl; // 1 } // ptr1 析构,对象被删除
自定义删除器:
cpp// 使用函数指针 void customDeleter(int* p) { std::cout << "Custom deleter called" << std::endl; delete p; } std::shared_ptr<int> ptr(new int(42), customDeleter); // 使用 lambda auto deleter = [](int* p) { std::cout << "Lambda deleter called" << std::endl; delete p; }; std::shared_ptr<int> ptr2(new int(42), deleter); // 数组删除器 std::shared_ptr<int> arr(new int[10], [](int* p) { delete[] p; });
get() 和 reset():
cppstd::shared_ptr<int> ptr = std::make_shared<int>(42); // 获取原始指针 int* rawPtr = ptr.get(); *rawPtr = 100; // 重置 ptr.reset(); // 释放当前对象 ptr.reset(new int(200)); // 管理新对象
weak_ptr
基本用法:
cpp// 创建 weak_ptr std::shared_ptr<int> shared = std::make_shared<int>(42); std::weak_ptr<int> weak = shared; // 检查是否过期 if (!weak.expired()) { std::cout << "Object still exists" << std::endl; } // 锁定获取 shared_ptr if (auto ptr = weak.lock()) { std::cout << *ptr << std::endl; }
解决循环引用:
cppclass Node { public: std::shared_ptr<Node> next; std::weak_ptr<Node> prev; // 使用 weak_ptr 避免循环引用 ~Node() { std::cout << "Node destroyed" << std::endl; } }; void createCycle() { auto node1 = std::make_shared<Node>(); auto node2 = std::make_shared<Node>(); node1->next = node2; node2->prev = node1; // weak_ptr 不会增加引用计数 // node1 和 node2 会被正确释放 }
观察者模式:
cppclass Subject { private: std::vector<std::weak_ptr<Observer>> observers; public: void addObserver(std::shared_ptr<Observer> obs) { observers.push_back(obs); } void notify() { for (auto it = observers.begin(); it != observers.end(); ) { if (auto obs = it->lock()) { obs->update(); ++it; } else { // 观察者已被删除,移除 it = observers.erase(it); } } } };
智能指针最佳实践
1. 优先使用 make_unique 和 make_shared
cpp// 推荐 auto ptr1 = std::make_unique<int>(42); auto ptr2 = std::make_shared<int>(42); // 不推荐 std::unique_ptr<int> ptr1(new int(42)); std::shared_ptr<int> ptr2(new int(42));
2. 避免使用裸指针管理智能指针
cpp// 不推荐 int* raw = new int(42); std::unique_ptr<int> ptr(raw); // 推荐 auto ptr = std::make_unique<int>(42);
3. 不要从函数返回裸指针
cpp// 不推荐 int* getPtr() { auto ptr = std::make_unique<int>(42); return ptr.get(); // 危险! } // 推荐 std::shared_ptr<int> getPtr() { return std::make_shared<int>(42); }
4. 使用智能指针管理数组
cpp// unique_ptr 数组 std::unique_ptr<int[]> arr(new int[10]); arr[0] = 1; // shared_ptr 数组(需要自定义删除器) std::shared_ptr<int> arr(new int[10], [](int* p) { delete[] p; });
5. 在函数参数中使用智能指针
cpp// 按值传递(增加引用计数) void process(std::shared_ptr<int> ptr) { // 使用 ptr } // 按引用传递(不增加引用计数) void process(const std::shared_ptr<int>& ptr) { // 使用 ptr } // 传递裸指针(如果函数不需要所有权) void process(int* ptr) { // 使用 ptr }
常见陷阱
1. 循环引用
cppclass A { public: std::shared_ptr<B> b; }; class B { public: std::shared_ptr<A> a; // 循环引用! }; // 解决:使用 weak_ptr class B { public: std::weak_ptr<A> a; // 正确 };
2. this 指针问题
cppclass MyClass { public: std::shared_ptr<MyClass> getShared() { return shared_from_this(); // 需要 enable_shared_from_this } }; class MyClass : public std::enable_shared_from_this<MyClass> { // ... };
3. 混合使用裸指针和智能指针
cppstd::shared_ptr<int> ptr = std::make_shared<int>(42); int* raw = ptr.get(); delete raw; // 错误!会导致双重释放
4. 在构造函数中传递 this
cppclass MyClass { public: MyClass() { // 错误!此时对象还未完全构造 registerObserver(this); } }; // 解决:使用两阶段构造 class MyClass { public: static std::shared_ptr<MyClass> create() { auto ptr = std::shared_ptr<MyClass>(new MyClass()); ptr->init(); return ptr; } private: MyClass() = default; void init() { registerObserver(shared_from_this()); } };
性能考虑
1. shared_ptr 的开销
- 引用计数:两个原子整数(控制块)
- 内存分配:控制块和对象可能分开分配
- 线程安全:引用计数操作是原子的
2. make_shared 的优势
- 一次内存分配:对象和控制块一起分配
- 更好的缓存局部性
- 减少内存碎片
3. weak_ptr 的开销
- 需要额外的弱引用计数
- lock() 操作需要原子操作
实际应用场景
1. 工厂模式
cppclass Factory { public: template <typename T, typename... Args> static std::shared_ptr<T> create(Args&&... args) { return std::make_shared<T>(std::forward<Args>(args)...); } }; auto obj = Factory::create<MyClass>(arg1, arg2);
2. 依赖注入
cppclass Service { public: Service(std::shared_ptr<Database> db) : db_(db) {} void execute() { db_->query("SELECT * FROM table"); } private: std::shared_ptr<Database> db_; };
3. 资源管理
cppclass ResourceManager { public: void addResource(std::unique_ptr<Resource> resource) { resources_.push_back(std::move(resource)); } Resource* getResource(size_t index) { return resources_[index].get(); } private: std::vector<std::unique_ptr<Resource>> resources_; };
注意事项
- 智能指针不能管理栈对象
- 避免在循环中创建大量 shared_ptr
- 注意智能指针的线程安全性
- 在多线程环境中使用 shared_ptr 时要注意引用计数的原子操作
- 考虑使用自定义分配器优化性能
- 理解智能指针的所有权语义,选择合适的类型