目录
1.shared_ptr VS weak_ptr
shared_ptr
: - 一种共享所有权的智能指针,它可以有多个指针同时指向同一块动态分配的内存 - 每个shared_ptr
都会维护一个引用计数(reference count),表示有多少个shared_ptr
指向相同的对象- 当引用计数变为零时(即没有任何shared_ptr
指向该对象),对象会被自动销毁- 使用场景: - 当多个对象需要共享同一个资源(内存对象)时使用- 自动管理动态分配的内存,避免手动释放内存weak_ptr
: - 一种不拥有对象所有权的智能指针,它不会增加引用计数-weak_ptr
的存在只是为了观察一个对象的状态是否还存在- 即:它可以用于检测shared_ptr
所管理的对象是否已经被销毁- 使用场景: - 解决shared_ptr
的循环引用问题- 在需要从外部访问对象,但不希望影响对象生命周期时使用 - 例如:在缓存、观察者模式等场景下使用- 注意事项: - 锁定(lock)使用: -weak_ptr
不能直接访问对象,需要通过lock()
返回一个shared_ptr
来安全访问对象-lock()
返回的shared_ptr
如果为空,表示原对象已经被销毁- 生命周期管理: -weak_ptr
不会影响对象的生命周期,它可以安全地防止悬垂指针的问题- 如果weak_ptr
所指向的对象已经销毁,lock()
返回的shared_ptr
会是空的,这样可以避免访问无效内存-weak_ptr
不能直接解引用访问对象,必须通过lock()
返回shared_ptr
来访问
2.unique_ptr VS shared_ptr
1.unique_ptr
unique_ptr
是一个独占所有权的智能指针,它在任何时刻只能有一个指针拥有所指向对象的所有权- 使用场景: - 独占资源所有权:当一个对象在其生命周期内不需要被多个指针共享时,使用
unique_ptr
是合适的 - 例如:当只想在一个类或函数中使用某个动态分配的资源时,可以使用unique_ptr
- 避免资源泄漏:在函数中创建的对象使用unique_ptr
时,可以确保函数结束时对象会被自动释放,即使发生了异常- 转移所有权:当需要将对象的所有权从一个地方转移到另一个地方时,可以使用std::move
将unique_ptr
传递给另一个unique_ptr
- 例如:将资源从一个函数返回到调用者中- 性能优势:由于unique_ptr
没有引用计数的开销,所以它比shared_ptr
更加轻量,适合性能敏感的场合
2.shared_ptr
shared_ptr
是一个共享所有权的智能指针,它允许多个shared_ptr
实例共同拥有同一个对象,当最后一个shared_ptr
被销毁时,所指向的对象才会被释放- 使用场景: - 多个所有者共享资源:当需要在多个地方共享同一个对象时,使用
shared_ptr
是合适的 - 典型的场景:观察者模式或回调函数中,当多个对象需要访问或管理相同的资源时- 生命周期管理:在复杂的对象关系中,shared_ptr
可以帮助管理对象的生命周期,防止悬空指针(dangling pointer)和内存泄漏- 动态创建对象的共享管理:当需要在函数间或不同模块间共享一个动态创建的对象时,可以使用shared_ptr
- 这样即使在不同作用域中访问该对象,也能保证它的生命周期是受控的- 回调和异步操作:当一个对象可能会被多个异步任务或回调函数引用时,使用shared_ptr
可以避免对象在任务执行时被提前销毁
3.选择原则
- 首选
unique_ptr
:如果资源的所有权不需要共享,总是优先考虑使用unique_ptr
,因为它更简单、开销更小 - **使用
shared_ptr
**:当资源需要被多个对象或多个线程共享时,使用shared_ptr
是合适的选择
4.注意事项
- 循环引用问题:
shared_ptr
可能会引发循环引用问题(即对象互相引用,导致引用计数永远不为零) - 解决方案:使用weak_ptr
打破这种循环 - 性能:由于
shared_ptr
需要维护引用计数,所以在性能要求高的场景中应尽量减少不必要的shared_ptr
复制
3.std::make_*
1.优势
- 性能优势: - 内存分配优化: -
std::make_shared
通过单次内存分配,为对象和shared_ptr
的控制块分配空间- 传统方式(std::shared_ptr<MyClass> ptr(new MyClass());
)需要两次内存分配- 一次为MyClass
对象分配内存- 一次为shared_ptr
的控制块(包含引用计数等)分配内存- 这种优化减少了内存碎片,并提高了内存分配和释放的效率,特别是在频繁创建和销毁对象的场景中- **缓存局部性(Cache Locality)**:由于std::make_shared
将对象和控制块放在同一内存块中,因此在访问这些数据时能更好地利用CPU缓存,从而提高程序的性能 - 异常安全性: - 在
new
操作符和智能指针结合使用时,如果对象的构造函数在shared_ptr
控制块创建之前抛出异常,会导致内存泄漏-std::make_*
系列函数在分配内存和构造对象时采用单一、无异常的原子操作,确保在构造函数失败时内存能被正确释放 - 代码简洁性和可读性: - 使用
std::make_*
系列函数时,代码更加简洁,不需要显式地使用new
操作符- 这不仅减少了代码量,还使代码的意图更加清晰 - 避免误用和内存管理错误:手动使用
new
和智能指针结合时容易引入误用,导致内存泄漏或多次释放 - 多重释放:当直接使用new
来创建对象并赋值给多个shared_ptr
时,可能会导致多重释放错误 - 错误示例:std::shared_ptr<MyClass>ptr1(newMyClass());// 错误!ptr1 和 ptr2 管理相同的对象,会导致双重释放std::shared_ptr<MyClass>ptr2(ptr1.get());
- 正确用法:// 正确, ptr1 和 ptr2 共享相同的控制块auto ptr = std::make_shared<MyClass>();
- 避免裸指针管理不当:std::make_*
避免了手动使用new
和delete
操作符,从而减少了由于裸指针管理不当导致的内存泄漏和未定义行为 - 灵活性和一致性:
std::make_*
系列函数为所有标准库的智能指针提供了一致的工厂函数接口 - 如std::make_unique
、std::make_shared
等- 这些工厂函数能够创建对象并返回相应的智能指针,使得代码更加一致 - **自动推导类型(Type Deduction)**:
std::make_*
系列函数利用C++11的类型推导特性,使得在创建智能指针时不必显式地指定类型,避免了冗长的类型声明// 传统方法std::shared_ptr<std::vector<int>>ptr(new std::vector<int>);// 使用 std::make_sharedauto ptr = std::make_shared<std::vector<int>>();
- 特定场景下的优势:
std::make_shared
和std::make_unique
在某些特殊场景下提供了更好的语义和性能 - 例如:使用std::make_shared
可以在多线程环境下更加高效,因为它减少了构造对象时的竞争条件
2.何时不用?
- 自定义删除器:如果需要自定义删除器来管理对象生命周期,不能使用
std::make_shared
,因为它的内存布局决定了必须使用默认删除器(delete
) - 需要使用裸指针接口:当需要与C API交互并且需要传递裸指针时,可能需要手动管理智能指针的创建和销毁
- 特殊内存分配要求:当需要在特定内存池中分配对象时,可能需要自定义的分配策略,这时
std::make_shared
不再适用
本文转载自: https://blog.csdn.net/qq_37281656/article/details/142632392
版权归原作者 DieSnowK 所有, 如有侵权,请联系我们删除。
版权归原作者 DieSnowK 所有, 如有侵权,请联系我们删除。