智能指针之std::shared_ptr<T>详解

基础介绍

c++11版本引入了智能指针,其中std::shared_ptr<T>是其中一种,这是一个模板类,它的类的简化实现如下所示:

template<typename T>
class shared_ptr {
private:
    T* ptr;           // 指向管理的对象
    int* count;       // 引用计数

    // 释放资源的辅助函数
    void release() {
        if (count) {
            (*count)--;
            if (*count == 0) {
                delete ptr;
                delete count;
            }
        }
    }

public:
    // 默认构造函数
    shared_ptr() : ptr(nullptr), count(nullptr) {}

    // 通过原始指针构造
    explicit shared_ptr(T* p) : ptr(p) {
        count = new int(1);
    }

    // 拷贝构造函数
    shared_ptr(const shared_ptr& other) : ptr(other.ptr), count(other.count) {
        if (count) {
            (*count)++;
        }
    }

    // 移动构造函数
    shared_ptr(shared_ptr&& other) noexcept : ptr(other.ptr), count(other.count) {
        other.ptr = nullptr;
        other.count = nullptr;
    }

    // 析构函数
    ~shared_ptr() {
        release();
    }

    // 拷贝赋值运算符
    shared_ptr& operator=(const shared_ptr& other) {
        if (this != &other) {
            // 释放当前资源
            release();
            
            // 复制新资源
            ptr = other.ptr;
            count = other.count;
            if (count) {
                (*count)++;
            }
        }
        return *this;
    }

    // 移动赋值运算符
    shared_ptr& operator=(shared_ptr&& other) noexcept {
        if (this != &other) {
            // 释放当前资源
            release();
            
            // 移动资源
            ptr = other.ptr;
            count = other.count;
            other.ptr = nullptr;
            other.count = nullptr;
        }
        return *this;
    }

    // 重载操作符
    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }
    
    // 获取原始指针
    T* get() const { return ptr; }
    
    // 获取引用计数
    int use_count() const { return count ? *count : 0; }
    
    // 检查是否为空
    bool operator bool() const { return ptr != nullptr; }
    
    // 重置指针
    void reset(T* p = nullptr) {
        release();
        ptr = p;
        if (p) {
            count = new int(1);
        } else {
            count = nullptr;
        }
    }
};

依赖的头文件:#include <memory>

std::shared_ptr<T>是一个共享智能指针,多个共享指针对象可以共享同一片数,之所以称为智能指针是因为它可以自动实现资源的释放管理(通过数据控制块中的引用计数来实现),这与new/delete不同,new申请的数据块需要手动delete删除。std::shared_ptr<T>对象的内存结构如下:

a (shared_ptr对象)
    |--> ptr指针 --------→ [指向对应实际的数据块]
    |--> control block指针 → [Heap: 数据控制块]
                            |- use_count = 1
                            |- weak_count = 1
                            |- deleter
                            |- allocator
 

 从上面的内存结构可以看出,shared_ptr对象包含两个成员,且都是指针:

  • ptr指针:指向实际的数据块
  • 数据库控制块指针:指向数据控制块,在这个控制块中包含了引用数,弱引用数等信息

思考:若两个shard_ptr对象做复制操作,内存会发生什么?

请看下面的代码:

class MyClass
{
    private:
        int a;
        std::string str;
    public:
        MyClass(int value, std::string string):a(value),str(string){}
}

auto a = std::make_shared<MyClass>(10, "hello");
auto b = a; //请思考这个操作会发生什么?

1 初始状态:执行auto shardPtr = std::make_shared<MyClass>(10, "hello");后

sharedPtr的内存布局如下:

Stack:
a (shared_ptr对象)
    |--> ptr指针 --------→ [Heap: MyClass对象(10,"hello")]
    |--> control block指针 → [Heap: 控制块]
                            |- use_count = 1
                            |- weak_count = 1
                            |- deleter
                            |- allocator

2 拷贝构造过程:auto b = a;

std::shared_ptr<T>类实现了拷贝构造函数,因为a是一个左值,所以会触发拷贝构造函数的调用;shared_ptr的拷贝构造函数简化实现如下所示:

// 简化的拷贝构造函数实现
template<typename T>
shared_ptr<T>::shared_ptr(const shared_ptr& other) noexcept
{
    this->ptr = other.ptr;           // 复制指向管理对象的指针
    this->control_block = other.control_block;  // 复制指向控制块的指针
    if (control_block) {
        control_block->use_count++;   // 原子地增加引用计数
    }
}

从上面的简化实现看到,新的shared_ptr对象b仅仅是将a对象的两个指针拷贝到自己的对象中,所以说这两个对象指向的区域完全相同。

内部布局的变化:

Before:
Stack:
a (shared_ptr对象)
    |--> ptr指针 --------→ [Heap: MyClass(10,"hello")]
    |--> control block指针 → [Heap: 控制块]
                            |- use_count = 1
                            |- weak_count = 1
                            |- deleter
                            |- allocator

After:
Stack:
a (shared_ptr对象)
    |--> ptr指针 -------------------→              [Heap:MyClass(10,"hello")]
    |--> control block指针 ─┐   ↑
                           ↓              |    ↑                 
b (shared_ptr对象)             |   ↑                  [Heap: 控制块]
    |--> ptr指针 -----------------→↑                 |- use_count = 2
    |--> control block指针 ─┘                    |- weak_count = 1
                                                                |- deleter
                                                                |- allocator

可以看到两个shared_ptr的ptr指针指向同一块数据区域;control block指针也是指向同一个数据控制块。

线程安全性

 在shared_ptr对象中核心是修改引用技术,c++对引用计数的修改是原子操作:

// 在控制块中的引用计数增加是原子操作
atomic_increment(&control_block->use_count);

该原子操作确保了在多线程环境下使用shared_ptr对象。也就是说在不同的线程中操作同一个shared_ptr是不需要加锁的。

// 在不同线程中同时拷贝是安全的
void thread1() {
    auto b = a;  // 安全
}

void thread2() {
    auto c = a;  // 安全
}

总结

  1. std::shared_ptr对象是需要额外占用内存空间的,对象大小一般为2个指针变量的长度
  2. shared_ptr对象包含的是两个指针变量
  3. 当shard_ptr对象数据库块指针的引用变量变为0时,自动触发ptr指向的数据块的内存释放

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值