26.unique_ptr和shared_ptr区别
🔬 unique_ptr和shared_ptr区别
📖 内容概览
unique_ptr和shared_ptr是C++11引入的两种智能指针,它们都用于自动管理动态内存,避免内存泄漏。但它们在所有权管理、引用计数、使用场景等方面存在显著差异。本文将详细解析这两种智能指针的核心区别、使用方法、技术原理和最佳实践。
🎯 核心概念
🧩 unique_ptr核心特性
unique_ptr是一种独占所有权的智能指针:
- 独占性:一个对象只能被一个unique_ptr管理
- 无引用计数:不维护引用计数,性能更高
- 移动语义:支持所有权转移,不支持拷贝
- 自动释放:离开作用域时自动调用delete
- RAII机制:资源获取即初始化
🧩 shared_ptr核心特性
shared_ptr是一种共享所有权的智能指针:
- 共享性:一个对象可以被多个shared_ptr管理
- 引用计数:维护引用计数,当计数为0时释放资源
- 拷贝语义:支持拷贝和赋值
- 线程安全:引用计数的增减是线程安全的
- 自动释放:当最后一个shared_ptr离开作用域时释放资源
🔄 引用计数机制
shared_ptr的引用计数机制:
- 增加引用计数:拷贝shared_ptr、赋值shared_ptr
- 减少引用计数:shared_ptr离开作用域、调用reset()、赋值新值
- 释放资源:当引用计数减少到0时,调用delete释放资源
🛠️ 代码示例
📝 基本使用对比
#include <iostream>#include <memory>#include <string>using namespace std;void testBasicUsage() { cout << "\n=== 基本使用对比 ===" << endl;
// shared_ptr的基本使用 { shared_ptr<string> sp1 = make_shared<string>("shared_ptr test"); shared_ptr<string> sp2 = sp1; // 拷贝,引用计数+1 shared_ptr<string> sp3(sp1); // 拷贝构造,引用计数+1
cout << "sp1: " << *sp1 << endl; cout << "sp2: " << *sp2 << endl; cout << "sp3: " << *sp3 << endl; cout << "shared_ptr引用计数: " << sp1.use_count() << endl; // 输出:3 }
// unique_ptr的基本使用 { unique_ptr<string> up1 = make_unique<string>("unique_ptr test"); // unique_ptr<string> up2 = up1; // 错误:不支持拷贝 unique_ptr<string> up2 = move(up1); // 正确:支持移动语义 cout << "up2: " << *up2 << endl; // cout << "up1: " << *up1 << endl; // 错误:up1已释放所有权 }}int main() { testBasicUsage(); return 0;2. 所有权转移
class Test {private: int id_;public: Test(int id) : id_(id) { cout << "🏗️ Test构造:id=" << id_ << endl; }
~Test() { cout << "💥 Test析构:id=" << id_ << endl; }
void show() { cout << "🔍 Test::show() called, id=" << id_ << endl; }};void testOwnershipTransfer() { cout << "\n=== 所有权转移 ===" << endl;
// unique_ptr的所有权转移 { unique_ptr<Test> up1(new Test(1)); cout << "up1拥有所有权" << endl;
// 使用release()转移所有权 Test* raw_ptr = up1.release(); cout << "up1释放所有权,raw_ptr管理" << endl;
// 使用reset()转移所有权 unique_ptr<Test> up2; up2.reset(raw_ptr); cout << "up2获得所有权" << endl;
// 使用移动语义转移所有权 unique_ptr<Test> up3 = move(up2); cout << "up3获得所有权" << endl; }
// shared_ptr的所有权共享 { shared_ptr<Test> sp1 = make_shared<Test>(2); cout << "sp1引用计数: " << sp1.use_count() << endl;
{ shared_ptr<Test> sp2 = sp1; cout << "sp2引用计数: " << sp2.use_count() << endl;
{ shared_ptr<Test> sp3(sp1); cout << "sp3引用计数: " << sp3.use_count() << endl; } // sp3离开作用域,引用计数-1 } // sp2离开作用域,引用计数-1 } // sp1离开作用域,引用计数-1,变为0,释放资源}
int main() { testOwnershipTransfer(); return 0;}3. 工厂模式示例
#include <iostream>#include <memory>#include <string>using namespace std;
class Product {private: string name_;public: Product(const string& name) : name_(name) { cout << "🏗️ Product构造:" << name_ << endl; }
virtual ~Product() { cout << "💥 Product析构:" << name_ << endl; }
virtual void use() = 0;};class ConcreteProductA : public Product {public: ConcreteProductA() : Product("ConcreteProductA") {}
void use() override { cout << "🔧 ConcreteProductA::use()" << endl; }};
class ConcreteProductB : public Product {public: ConcreteProductB() : Product("ConcreteProductB") {}
void use() override { cout << "🔧 ConcreteProductB::use()" << endl; }};// 使用unique_ptr的工厂模式unique_ptr<Product> createProductUnique(const string& type) { if (type == "A") { return make_unique<ConcreteProductA>(); } else if (type == "B") { return make_unique<ConcreteProductB>(); } return nullptr;}
// 使用shared_ptr的工厂模式shared_ptr<Product> createProductShared(const string& type) { if (type == "A") { return make_shared<ConcreteProductA>(); } else if (type == "B") { return make_shared<ConcreteProductB>(); } return nullptr;}
void testFactoryPattern() { cout << "\n=== 工厂模式示例 ===" << endl; // 使用unique_ptr的工厂 { auto product1 = createProductUnique("A"); if (product1) { product1->use(); } auto product2 = createProductUnique("B"); if (product2) { product2->use(); } } // 使用shared_ptr的工厂 { auto product1 = createProductShared("A"); auto product2 = product1; // 共享所有权 cout << "shared_ptr引用计数: " << product1.use_count() << endl; }}
int main() { testFactoryPattern(); return 0;}� 详细对比
1. 核心区别对比
| 特性 | unique_ptr | shared_ptr |
|---|---|---|
| 所有权 | 独占所有权 | 共享所有权 |
| 引用计数 | 无 | 有 |
| 性能 | 更高(无引用计数开销) | 较低(引用计数增减开销) |
| 拷贝操作 | 禁止拷贝,支持移动 | 支持拷贝和赋值 |
| 线程安全 | 不保证线程安全 | 引用计数增减是线程安全的 |
| 内存开销 | 较小(仅存储指针) | 较大(存储指针+引用计数指针) |
| API复杂度 | 简单 | 复杂(支持更多操作) |
| 循环引用 | 不会产生 | 可能产生,需要weak_ptr解决 |
| 数组支持 | 原生支持(unique_ptr<T[]>) | 需要自定义删除器 |
2. 操作对比
| 操作 | unique_ptr | shared_ptr | | 构造函数 | 支持默认构造、带参构造、移动构造 | 支持默认构造、带参构造、拷贝构造、移动构造 | | 拷贝赋值 | 禁止 | 支持 | | 移动赋值 | 支持 | 支持 | | reset() | 支持 | 支持 | | release() | 支持(释放所有权) | 不支持 | | swap() | 支持 | 支持 | | get() | 支持(获取原始指针) | 支持(获取原始指针) | | use_count() | 不支持 | 支持(获取引用计数) | | unique() | 不支持 | 支持(检查是否独占) | | operator bool() | 支持(检查是否为空) | 支持(检查是否为空) | | operator-> | 支持 | 支持 | | operator* | 支持 | 支持 |
3. 适用场景对比
| 场景 | 推荐使用 | 不推荐使用 |
|---|---|---|
| 独占资源 | unique_ptr | shared_ptr |
| 共享资源 | - | shared_ptr |
| 性能敏感场景 | unique_ptr | shared_ptr |
| 容器元素 | unique_ptr | shared_ptr(除非需要共享) |
| 工厂模式返回值 | unique_ptr | shared_ptr(除非需要共享) |
| 多线程环境 | - | shared_ptr(引用计数线程安全) |
| 循环数据结构 | - | shared_ptr + weak_ptr |
| 数组管理 | unique_ptr<T[]> | shared_ptr |
⚠️ 注意事项
1. unique_ptr注意事项
- 禁止拷贝:不要尝试拷贝unique_ptr,会导致编译错误
- 移动后状态:移动后的unique_ptr处于有效但未定义的状态,不应再使用
- 数组支持:使用unique_ptr<T[]>管理数组,自动调用delete[]
- 自定义删除器:可以指定自定义删除器,用于管理特殊资源
- release()的使用:release()返回的原始指针需要手动管理,或传递给另一个智能指针
2. shared_ptr注意事项
- 循环引用:两个或多个shared_ptr互相引用会导致内存泄漏,需要使用weak_ptr解决
- 性能开销:引用计数的增减会带来性能开销,频繁拷贝时需注意
- 线程安全:shared_ptr本身不是线程安全的,只有引用计数的增减是线程安全的
- make_shared的使用:优先使用make_shared创建shared_ptr,更高效
- 避免裸指针:不要将同一个裸指针赋值给多个shared_ptr,会导致双重释放
3. 共同注意事项
- 不要手动释放资源:智能指针会自动释放,手动释放会导致双重释放
- 避免悬空指针:不要保存智能指针管理的原始指针,可能导致悬空指针
- 注意作用域:智能指针离开作用域时会自动释放资源
- 不要与裸指针混用:尽量避免将智能指针与裸指针混用,容易导致错误
- 注意异常安全:智能指针能保证异常情况下资源的正确释放
📋 总结
1. 选择建议
- 优先使用unique_ptr:当资源只需独占所有权时,unique_ptr是最佳选择,性能更高
- 使用shared_ptr:当资源需要共享所有权时,使用shared_ptr
- 避免不必要的共享:共享会带来引用计数开销,尽量使用unique_ptr
- 使用make_unique和make_shared:更安全、更高效地创建智能指针
2. 最佳实践
// 推荐使用make_unique和make_shared创建智能指针auto up = make_unique<MyClass>();auto sp = make_shared<MyClass>();// 禁止将同一个裸指针赋值给多个shared_ptrMyClass* raw = new MyClass();shared_ptr<MyClass> sp1(raw);// shared_ptr<MyClass> sp2(raw); // 错误:会导致双重释放// 避免循环引用class A {public: shared_ptr<B> b_ptr;};class B { shared_ptr<A> a_ptr;// 改为使用weak_ptr weak_ptr<A> a_ptr; // 使用weak_ptr避免循环引用// 使用unique_ptr管理数组unique_ptr<int[]> arr(new int[10]);arr[0] = 42;arr[1] = 100;3. 核心要点回顾
- unique_ptr:独占所有权,无引用计数,性能高,支持移动语义
- shared_ptr:共享所有权,有引用计数,性能较低,支持拷贝语义
- 选择依据:根据资源所有权需求选择合适的智能指针
- 避免常见错误:循环引用、裸指针混用、手动释放资源
- 优先使用工厂函数:make_unique和make_shared更安全高效 通过合理选择和使用unique_ptr和shared_ptr,可以实现安全、高效的内存管理,避免内存泄漏和其他内存相关问题。在实际开发中,应根据具体场景和需求选择合适的智能指针,遵循最佳实践,编写高质量的C++代码。