看柠檬微趣c++客户端一面面经,发现shared_ptr手撕不了一点,顺便再复习一下智能指针吧。(真是面试造火箭)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| #include<cstddef> struct ControlBlock { size_t shared_count;//引用计数 ControlBlock():shared_count(1){} }; template <typename T> class Shared_ptr { private: T* ptr; ControlBlock* ctrl; void release()//一个私有的释放函数 { if(ctrl) { --ctrl->shared_count; if(!ctrl->shared_count) { delete ctrl; delete ptr;//如果引用计数为0,则自动销毁该引用计数块和对应的指针 } ctrl=nullptr; ptr=nullptr; } } public: //默认构造函数 Shared_ptr():ptr(nullptr),ctrl(nullptr); //有参数构造函数 Shared_ptr(T* p):ptr(p),ctrl(p?new ControlBlock:nullptr) //拷贝构造函数(注意拷贝构造函数是const,且要增加引用计数) Shared_ptr(const Shared_ptr& other) { ctrl=other.ctrl; ptr=other.ptr; if(ctrl) ++ctrl->shared_count; } //移动构造函数(注意是右值引用,不增加引用计数,要将原指针置空) Shared_ptr(Shared_ptr&& other) noexcept { ctrl=other.ctrl; ptr=other.ptr; other.ptr=nullptr; other.ctrl=nullptr; } //拷贝构造运算符 Shared_ptr& operater=(const Shared_ptr& other) { if(this!=other) { release(); ptr=other.ptr; ctrl=other.ctrl; if(ctrl) { ++ctrl->shared_count; } } return *this; } Shared_ptr& operater=(Shared_ptr&& other) noexcept { if(this!=other) { release(); ptr=other.ptr; ctrl=other.ctrl; other.ptr=nullptr; other.ctrl=nullptr; } return *this; } ~Shared_ptr() { release(); } //重载操作符,解引用和取地址 T* operater->() const {return ptr;} T& operater&() const {return *ptr;} // 获取原始指针 T* get() const { return ptr; }
// 获取引用计数 size_t use_count() const { return ctrl ? ctrl->shared_count : 0; }
// 检查是否为空 bool empty() const { return ptr == nullptr; } };
|
C++ 智能指针:shared_ptr
, weak_ptr
, unique_ptr
, scoped_ptr
智能指针是 C++ 中用于自动管理动态分配内存的工具,避免手动 new
和 delete
带来的内存泄漏或悬空指针问题。以下是对 shared_ptr
、weak_ptr
、unique_ptr
和 scoped_ptr
的特点、区别和应用场景的对比。
1. shared_ptr
特点
- 共享所有权:多个
shared_ptr
可指向同一对象,通过引用计数 (use_count
) 管理资源。
- 自动释放:当最后一个
shared_ptr
销毁或重置时,释放资源。
- 线程安全:引用计数操作是线程安全的,但对象访问需用户保证。
- 支持自定义删除器:可指定释放资源的方式。
- 标准库实现:C++11 引入,位于
<memory>
。
实现机制
- 使用控制块存储引用计数和删除器。
- 拷贝增加引用计数,析构减少引用计数,计数为 0 时释放资源。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <memory> #include <iostream>
struct Test { ~Test() { std::cout << "Test destroyed\n"; } };
int main() { auto sp1 = std::make_shared<Test>(); std::cout << "sp1 use_count: " << sp1.use_count() << "\n"; { auto sp2 = sp1; std::cout << "sp2 use_count: " << sp2.use_count() << "\n"; } std::cout << "sp1 use_count: " << sp1.use_count() << "\n"; }
|
应用场景
- 共享资源:多个对象需要共享同一资源(如共享配置对象)。
- 动态资源管理:需要动态分配内存且多个指针可能引用。
- 复杂生命周期:对象生命周期不明确,需自动管理。
注意事项
- 避免循环引用(可能导致内存泄漏)。
- 使用
std::make_shared
构造以优化性能(单次分配)。
2. weak_ptr
特点
- 非拥有引用:指向
shared_ptr
管理的对象,但不控制其生命周期。
- 避免循环引用:不会增加
shared_ptr
的引用计数。
- 需检查有效性:通过
lock()
获取 shared_ptr
检查对象是否存活。
- 标准库实现:C++11 引入,位于
<memory>
。
实现机制
- 与
shared_ptr
共享控制块,跟踪对象但不影响引用计数。
- 提供
expired()
检查对象是否已释放,lock()
返回有效 shared_ptr
或空指针。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <memory> #include <iostream>
int main() { auto sp = std::make_shared<int>(42); std::weak_ptr<int> wp = sp; std::cout << "wp expired: " << wp.expired() << "\n"; { auto sp2 = wp.lock(); if (sp2) std::cout << "Value: " << *sp2 << "\n"; } sp.reset(); std::cout << "wp expired: " << wp.expired() << "\n"; }
|
应用场景
- 打破循环引用:如树结构中父子节点互相引用。
- 缓存:临时访问共享资源但不控制其生命周期。
- 事件监听:观察对象状态而不延长其生命周期。
注意事项
- 必须通过
lock()
访问对象,检查是否有效。
- 不能直接解引用,需转换为
shared_ptr
。
3. unique_ptr
特点
- 独占所有权:单一指针拥有资源,禁止拷贝,只能移动。
- 轻量高效:无引用计数开销,性能优于
shared_ptr
。
- 自动释放:离开作用域时自动删除资源。
- 支持自定义删除器:可指定资源释放方式。
- 标准库实现:C++11 引入,位于
<memory>
。
实现机制
- 使用 RAII 管理资源,析构时调用删除器。
- 移动构造/赋值转移所有权,原始指针置空。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <memory> #include <iostream>
struct Test { ~Test() { std::cout << "Test destroyed\n"; } };
int main() { auto up = std::make_unique<Test>(); auto up2 = std::move(up); std::cout << "up empty: " << (up == nullptr) << "\n"; }
|
应用场景
- 独占资源:对象只需要一个所有者(如文件句柄、单例资源)。
- 工厂模式:返回独占对象的所有权。
- 性能敏感场景:需要高效内存管理。
注意事项
- 使用
std::make_unique
(C++14) 构造以避免异常安全问题。
- 适合需要明确所有权的场景。
4. scoped_ptr
(Boost)
特点
- 非标准库:来自 Boost 库(C++ 标准库无
scope_ptr
,可能是指 boost::scoped_ptr
)。
- 严格作用域所有权:不可拷贝、不可移动,仅在定义作用域内有效。
- 轻量:无引用计数,简单高效。
- 自动释放:离开作用域时删除资源。
- 不支持自定义删除器:固定使用
delete
。
实现机制
- 类似
unique_ptr
但更严格,禁止任何所有权转移。
- 析构时直接删除资源。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12
| #include <boost/scoped_ptr.hpp> #include <iostream>
struct Test { ~Test() { std::cout << "Test destroyed\n"; } };
int main() { boost::scoped_ptr<Test> sp(new Test()); }
|
应用场景
- 严格局部资源:确保资源在单一作用域内使用(如函数内临时对象)。
- 简单 RAII:无需转移所有权的场景。
- 替换原始指针:提高局部变量的安全性。
注意事项
- 不支持所有权转移,灵活性低于
unique_ptr
。
- Boost 库依赖,可能不适用于标准 C++ 项目。
对比总结
特性 |
shared_ptr |
weak_ptr |
unique_ptr |
scoped_ptr (Boost) |
所有权 |
共享(引用计数) |
无所有权(弱引用) |
独占(单一所有者) |
独占(不可转移) |
拷贝 |
支持 |
支持 |
不支持 |
不支持 |
移动 |
支持 |
支持 |
支持 |
不支持 |
自定义删除器 |
支持 |
无(依赖 shared_ptr ) |
支持 |
不支持 |
线程安全 |
引用计数线程安全 |
引用计数线程安全 |
无需线程安全(独占) |
无需线程安全(独占) |
性能开销 |
较高(引用计数+控制块) |
中等(依赖 shared_ptr ) |
低(无引用计数) |
最低(无引用计数、无转移) |
标准库 |
C++11 (<memory> ) |
C++11 (<memory> ) |
C++11 (<memory> ) |
Boost 库 |
数学表示
shared_ptr
的引用计数可表示为:
[
\text{use_count}(p) = \sum_{i} \text{shared_ptr}_i \text{ pointing to } p
]
其中,资源在 (\text{use_count} = 0) 时释放。
weak_ptr
的有效性检查:
[
\text{lock}() = \begin{cases}
\text{shared_ptr}(p), & \text{if } \text{use_count} > 0 \
\text{nullptr}, & \text{otherwise}
\end{cases}
]
unique_ptr
和 scoped_ptr
的所有权单一,生命周期严格绑定作用域。
应用场景总结
- **
shared_ptr
**:多所有者共享资源,如对象池、复杂数据结构。
- **
weak_ptr
**:打破循环引用、缓存、事件监听。
- **
unique_ptr
**:独占资源、工厂模式、传递所有权。
- **
scoped_ptr
**:严格局部资源管理,简单 RAII。
注意事项
- 优先使用
std::make_shared
和 std::make_unique
构造智能指针以确保异常安全。
- 避免将原始指针直接传入智能指针构造函数(如
shared_ptr<T>(new T)
),可能导致重复删除。
scoped_ptr
非标准库,使用需依赖 Boost,若需类似功能,推荐 unique_ptr
。