目录
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
不再适用