目录
1.shared_ptr VS weak_ptr2.unique_ptr VS shared_ptr1.unique_ptr2.shared_ptr3.选择原则4.注意事项 3.std::make_*1.优势2.何时不用?
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(new MyClass());// 错误!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不再适用