【智能指针】std::shared_ptr

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

std::shared_ptr 是 C++ 中用于共享所有权的智能指针,通过引用计数机制管理动态资源。以下是其核心特性、使用场景及注意事项的详细说明:


1. 核心特性

特性说明
共享所有权多个 shared_ptr 可指向同一对象,引用计数归零时自动释放资源
原子引用计数引用计数的增减是线程安全的(但指向的对象本身需用户保证线程安全)
自定义删除器支持自定义资源释放逻辑(如文件句柄、网络连接)
内存高效std::make_shared 将对象和控制块合并分配,减少内存碎片

2. 基础用法

创建与初始化
  • 推荐方式​(异常安全且高效):
    auto sp1 = std::make_shared<Widget>();  // 创建 Widget 对象
    auto sp2 = std::make_shared<int[]>(10); // C++20 起支持动态数组
  • 传统方式​(避免使用裸指针直接初始化):
    std::shared_ptr<Widget> sp3(new Widget());  // 潜在风险:若后续代码抛出异常可能泄漏
共享所有权
auto sp4 = sp1;         // 引用计数 +1
auto sp5 = std::move(sp1); // 移动语义,sp1 变为 nullptr,计数不变
资源释放
sp2.reset();            // 主动释放资源,引用计数 -1
sp4 = nullptr;          // 等价于 reset()

3. 高级用法

自定义删除器
// 示例:管理文件句柄
std::shared_ptr<FILE> file(
    fopen("data.txt", "r"), 
    [](FILE* f) { 
        if (f) {
            fclose(f); 
            std::cout << "文件已关闭\n";
        }
    }
);
别名构造(Alias Constructor)​
class Base { /* ... */ };
class Derived : public Base { /* ... */ };

auto derived = std::make_shared<Derived>();
std::shared_ptr<Base> base(derived, derived.get()); // 共享引用计数,但指向基类部分
弱引用与循环引用解决
class Parent;
class Child {
public:
    std::shared_ptr<Parent> parent;  // 错误:导致循环引用
    // 修正方案:
    std::weak_ptr<Parent> parent;    // 弱引用不增加计数
};

4. 性能与线程安全

场景性能影响解决方案
高频创建/销毁原子操作引用计数可能成为瓶颈(尤其在多线程环境)改用 unique_ptr 或对象池
跨线程传递引用计数线程安全,但对象本身需同步使用互斥锁保护对象数据
控制块分离若通过 new 初始化,对象和控制块分离,可能引发缓存不命中优先使用 std::make_shared

5. 常见错误与规避

错误 1:同一裸指针初始化多个shared_ptr
Widget* raw = new Widget();
std::shared_ptr<Widget> sp6(raw);
// std::shared_ptr<Widget> sp7(raw); // 错误!会导致重复释放

修正:始终通过已存在的 shared_ptr 复制,或使用 make_shared

错误 2:忽略循环引用
struct Node {
    std::shared_ptr<Node> next; // 循环引用导致内存泄漏
};

修正:将至少一个指针改为 weak_ptr

错误 3:误用get()返回的裸指针
auto sp8 = std::make_shared<int>(42);
int* raw_ptr = sp8.get();
delete raw_ptr;  // 错误!sp8 析构时会再次释放

修正:禁止手动释放由 shared_ptr 管理的资源。


6. 最佳实践

  1. 优先使用 std::make_shared

    • 合并内存分配,提升性能。
    • 保证异常安全(避免 new 和 shared_ptr 构造之间的异常)。
  2. 明确所有权语义

    • 仅在需要共享所有权时使用 shared_ptr,否则优先用 unique_ptr
  3. 跨模块边界谨慎传递

    • 动态库接口中避免直接传递 shared_ptr(不同模块可能使用不同堆管理器)。
  4. 性能敏感场景优化

    // 局部非原子计数优化(C++20 引入)
    std::shared_ptr<Widget> sp9 = std::make_shared<Widget>();
    std::shared_ptr<Widget> sp10 = std::shared_ptr(sp9); // 非原子递增(若仅在单线程使用)

示例:线程安全的对象缓存

#include <memory>
#include <mutex>
#include <unordered_map>

class Cache {
    std::mutex mtx;
    std::unordered_map<int, std::weak_ptr<Resource>> cache;

public:
    std::shared_ptr<Resource> get(int key) {
        std::lock_guard<std::mutex> lock(mtx);
        auto it = cache.find(key);
        if (it != cache.end()) {
            auto sp = it->second.lock(); // 尝试提升为 shared_ptr
            if (sp) return sp;
        }
        auto new_sp = std::make_shared<Resource>(key);
        cache[key] = new_sp; // 存储 weak_ptr
        return new_sp;
    }
};

总结

std::shared_ptr 是管理共享资源的利器,但需注意:

  • 优先使用 make_shared 保证安全和性能
  • 用 weak_ptr 打破循环引用
  • 避免与裸指针混用导致重复释放
  • 多线程中需额外保护共享对象本身

正确使用时,可显著降低内存泄漏和悬空指针风险,提升代码健壮性。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浩瀚之水_csdn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值