0


[C++][三种智能指针的适用场景]详细说明

目录


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::moveunique_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_*避免了手动使用newdelete操作符,从而减少了由于裸指针管理不当导致的内存泄漏和未定义行为
  • 灵活性和一致性std::make_*系列函数为所有标准库的智能指针提供了一致的工厂函数接口 - 如std::make_uniquestd::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_sharedstd::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 所有, 如有侵权,请联系我们删除。

“[C++][三种智能指针的适用场景]详细说明”的评论:

还没有评论