优秀开源内容转自公众号后端开发成长指南

背景:在C++中,手动管理资源(new/delete)容易出错,导致:内存泄漏,重复释放,异常安全等问题。

为了解决这些问题,C++11引入了智能指针:

std::unique_ptr:独占所有权,std::shared_ptr 共享所有权,通过引用计数来管理生命周期

原理:

unique_ptr:目标是独占所有权,不可复制,只能移动(move)

使用场景:资源唯一所有权,RAII风格自动释放;

核心问题是:避免内存泄漏,支持移动语义。

核心原理:

1.独占所有权:唯一指针拥有资源,拷贝被禁用

2.移动语义:通过移动实现资源转移:

3.RAII:生命周期结束自动释放。

3.性能特点:

内存占用小(只存储原始指针和删除器)

无额外引用计数开销

编译器开销低

异常安全,必变资源泄漏;

shared_ptr:目标是多方共享资源,引用计数控制生命周期。

使用场景:多线程共享对象,缓存管理。

核心问题:

自动管理生命周期;处理多线程环境下的引用计数(atomic)

避免循环引用问题

1.内部结构:核心在于引用计数和控制块。

shared_ptr<T>

├── T* ptr // 实际对象指针

└── ControlBlock* cb // 引用计数和删除器

控制块结构示例

2.核心逻辑:

1)构造shared_ptr:初始化use_count = 1;

  1. 拷贝shared_ptr:use_count++(原子操作)

3)析构shared_ptr:use_count--,若为0调用删除器销毁对象;

weak_ptr:不增加use_count,仅增加weak_count,用于解决循环引用。

3.关键实现细节:

线程安全:std::atomic 保证引用计数在多线程下正确;

控制块分离:

对象和控制快分离,可以支持make_shared内联分配(减少内存碎片)

循环引用问题:

两个shared_ptr互相循环引用会导致引用计数不为0

需要weak_ptr解决;

4.性能分析:

unique_ptr vs shared_ptr

1.内存占用:

小,指针+删除器; 控制块额外开销,atomic计数

  1. 拷贝/移动开销

禁止拷贝,移动开销小; 拷贝需要原子操作,移动开销小

3.多线程安全

依赖外部保护; 引用计数原子操作保证安全

关键区别:引用计数的原子性

cpp

cpp 复制代码
// ❌ unique_ptr - 所有权转移不是线程安全的
class BadExample {
    std::unique_ptr<Data> data;
public:
    // 线程1调用
    void thread1() {
        auto temp = std::move(data); // 危险!没有同步
        // data现在是nullptr
    }
    
    // 线程2调用
    void thread2() {
        if (data) { // 竞态条件!
            data->process(); // 可能崩溃
        }
    }
};

// ✅ shared_ptr - 引用计数操作是原子的
class GoodExample {
    std::shared_ptr<Data> data;
public:
    // 线程1调用
    void thread1() {
        auto local_copy = data; // 原子地增加引用计数
        if (local_copy) {
            local_copy->process(); // 安全使用
        }
    } // 原子地减少引用计数
    
    // 线程2调用  
    void thread2() {
        auto local_copy = data; // 原子地增加引用计数
        if (local_copy) {
            local_copy->process(); // 安全使用
        }
    } // 原子地减少引用计数
};

更好的对比示例

cpp

复制代码
// unique_ptr - 必须用锁保护所有权转移
class TaskProcessor {
    std::unique_ptr<Task> current_task;
    std::mutex task_mutex;
public:
    void process() {
        std::unique_ptr<Task> local_task;
        {
            std::lock_guard<std::mutex> lock(task_mutex);
            local_task = std::move(current_task); // 必须在锁内移动
        }
        if (local_task) {
            local_task->execute();
        }
    }
    
    void setTask(std::unique_ptr<Task> task) {
        std::lock_guard<std::mutex> lock(task_mutex); // 必须加锁
        current_task = std::move(task);
    }
};

// shared_ptr - 复制操作本身是线程安全的
class DataCache {
    std::shared_ptr<CachedData> cached_data;
    // 注意:这里不需要mutex保护shared_ptr的复制!
public:
    void updateCache(std::shared_ptr<CachedData> new_data) {
        cached_data = new_data; // 原子操作,无需加锁
    }
    
    std::shared_ptr<CachedData> getData() {
        return cached_data; // 原子操作,无需加锁
    }
    
    void process() {
        auto local_data = getData(); // 线程安全地获取副本
        if (local_data) {
            // 即使其他线程调用updateCache(),local_data仍然有效
            local_data->doWork(); 
        }
    }
};

4.循环引用风险

无; 有,需要weak_ptr

5.适用场景:

独占资源 RAII; 多方共享对象管理,缓存,异步任务。

核心思想:能用 unique_ptr 就用 unique_ptr,能静态分析生命周期,避免 atomic 开销;需要多方共享才用 shared_ptr,并结合 weak_ptr 避免循环引用。

总结:

unique_ptr:

独占资源所有权,低开销,RAII自动释放

通过移动语义实现所有权转移

shared_ptr:共享资源,通过引用计数和控制块管理生命周期

支持多线程,但有性能开销

循环引用需要weak_ptr解决。

在分布式系统

hotspot 对象尽量用 unique_ptr

异步回调、共享上下文用 shared_ptr

一句话总结

"能唯一所有权就用 unique_ptr,必须共享就用 shared_ptr,控制引用计数就是控制你的性能。"

复制代码
struct ControlBlock {
    std::atomic<size_t> use_count;   // 强引用计数
    std::atomic<size_t> weak_count;  // 弱引用计数
    Deleter deleter;                 // 删除器
    T* ptr;                          // 指向对象
};
相关推荐
你我约定有三17 小时前
面试tips--JVM(4)--Minor GC & Major GC & Full GC
jvm·面试·职场和发展
Li_yizYa17 小时前
JVM:内存区域划分、类加载的过程、垃圾回收机制
java·jvm
A尘埃17 小时前
企业级架构师综合能力项目案例二(项目性能优化方案JVM+数据库+缓存+代码JUC+消息中间件架构+服务熔断降级)
jvm·数据库·性能优化·架构师
ByteBlossom17 小时前
JVM核心机制:类加载与内存结构详解
jvm
善我17 小时前
JVM中产生OOM(内存溢出)的8种典型情况及解决方案
jvm
程序员江鸟17 小时前
Java面试实战系列【JVM篇】- JVM内存结构与运行时数据区详解(共享区域)
java·jvm·面试
ybq1951334543118 小时前
RabbitMinQ(模拟实现消息队列项目)02
jvm
ChillJavaGuy2 天前
Java中的四大引用类型强引用、软引用、弱引用、虚引用
java·开发语言·jvm·四大引用类型
C++chaofan2 天前
Spring Task快速上手
java·jvm·数据库·spring boot·后端·spring·mybatis