[2.3节]什么是shared_ptr共享的智能指针?

0 前言

涉及的代码可在repo中找到:https://github.com/leoda1/the-notes-of-cuda-programming/tree/main/code/CPU
C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。

C++里面的四个智能指针: auto_ptr ,unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持, 第一个已经被C++11弃用。使用这些智能指针时需要引用头文件<memory>

1 shared_ptr共享的智能指针

共享的智能指针 std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。再最后一个shared_ptr析构的时候,内存才会被释放。shared_ptr共享被管理对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个 shared_ptr对象销毁时,被管理对象自动销毁。简单来说,shared_ptr实现包含了两部分:

  • 一个指向堆上创建的对象的裸指针:raw_ptr
  • 一个指向内部隐藏的、共享的管理对象:share_count_object
  1. 初始化:

    使用new初始化,使用make_shared初始化,使用另一个shared_ptr初始化,使用unique_ptr初始化后move给shared_ptr,使用自定义删除器初始化等等。这里简单说明前三种,最推荐使用第二种(因为他更高效)。

    第一种是:std::shared_ptr<int> ptr(new int(42));

    第二种是:std::shared_ptr<int> ptr = std::make_shared<int>(42);

    第三种是:

    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
    std::shared_ptr<int> ptr2 = ptr1; // 拷贝初始化
    

    初始化的时候会涉及两个动态内存分配:

    1. 对象的内存分配:这是为了分配存储对象本身的内存,这一步是new表达式完成的。它会分配足够内存给这个int(42),并调用int型的构造函数来初始化这块内存。
    2. 控制块的内存分配std::shared_ptr需要一个额外的控制块来存储与对象管理相关的信息,比如引用计数和可自定义删除器。

    make_shared中只进行了一次就将这两个动态内存就分配完毕了,并且此时的对象和控制块的内存是相邻连续的,会提高缓存的使用效率。而第一种先new分配内存给int(42),再shared_ptr 构造函数为自己控制块分配另一块内存。

    对于一个未初始化的智能指针,这里会用到shared_ptr的第一个成员函数reset()方法来初始化。当智能指针有值的时候调用reset()会将当前管理的对象的引用计数减1。这个管理的对象的引用计数为0就会被销毁,没到0的话当前 shared_ptr 不再管理它。那么不管它了就结束了吗?不是,reset有三种重载形式:无参,带指针参数,以及带指针和删除器参数。定义如下:

    //释放当前管理的对象,并将 shared_ptr 置为空指针(即不再管理任何对象)。
    void reset() noexcept; 
    
    //释放当前管理的对象,并将 shared_ptr 指向 ptr 所指向的对象。ptr 必须是一个可以转换为 T* 
    //的指针类型(T 是 shared_ptr 管理的对象类型)。
    template <class Y>
    void reset(Y* ptr);
    
    //释放当前管理的对象,并将 shared_ptr 指向 ptr 所指向的对象,同时指定一个自定义的删除器 d。
    //删除器 d 是一个可调用对象,用于在 shared_ptr 不再管理对象时释放资源。
    template <class Y, class Deleter>
    void reset(Y* ptr, Deleter d);
    

    初始化部分范例代码:

    // init_sample.cpp
    #include <iostream>
    #include <memory>
    using namespace std;
    
    void test(shared_ptr<int> sp) {
        cout << "sp3.use_count() =" << sp.use_count() << endl;
    }
    
    int main() {
        auto sp1 = make_shared<int>(100);  //use make_shared
        shared_ptr<int> sp2(new int(100)); //use new to init
        cout << "sp1.use_count() =" << sp1.use_count() << endl;
        cout << "sp2.use_count() =" << sp2.use_count() << endl;
        shared_ptr<int> sp3(new int(100));
        test(sp3);
        cout << "sp4.use_count() =" << sp3.use_count() << endl;
    
        shared_ptr<int> p1;
        p1.reset(new int(1)); //带参数的reset会将p1指向int(1)
        shared_ptr<int> p2 = p1;
    
        // 现在p1 和 p2 都是指向int(1)
        cout << "p2.use_count() = " << p2.use_count()<< endl;//输出2
        cout << "p1.use_count() = " << p1.use_count()<< endl;//输出2
    
        p1.reset();   // 没有参数就是释放资源
        cout << "p2.use_count() = " << p2.use_count() << endl;//输出1
        cout << "p1.use_count() = " << p1.use_count()<< endl;//输出0
    
        return 0;
    }
    /* 
    sp1.use_count() =1
    sp2.use_count() =1
    sp3.use_count() =2
    sp4.use_count() =1
    p2.use_count() = 2
    p1.use_count() = 2
    p2.use_count() = 1
    p1.use_count() = 0
    */
    

    补充:这里会用到shared_ptr的第二个成员函数use_count():它是 std::shared_ptr 的一个成员函数,用来返回 当前 shared_ptr 所管理的对象 被多少个 shared_ptr 实例共享引用。

  2. 获取原始指针:

    当需要获取原始指针时,可以通过get方法来返回原始指针,代码如下所示:

    std::shared_ptr<int> ptr(new int(1));
    int *p = ptr.get(); //万一不小心 delete p;
    

    谨慎使用p.get()的返回值,如果你不知道其危险性则永远不要调用get()函数。

    p.get()的返回值就相当于一个裸指针的值,上述陷阱的所有错误都有可能发生, 遵守以下几个约定:

    • 不要保存p.get()的返回值 ,无论是保存为裸指针还是shared_ptr都是错误的。保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针
    • 不要delete p.get()的返回值 ,会导致对一块内存delete两次的错误
  3. 指定删除器:

    如果用shared_ptr管理非new对象或是没有析构函数的类时,应当为其写一个合适的删除器。

    
    #include <iostream>
    #include <memory>
    using namespace std;
    
    void deleter (int *p) {
        cout << "call Deleter delete p1" << endl;
        delete p;
    }
    
    int main() {
        shared_ptr<int> p1(new int(1), deleter);
        shared_ptr<int> p2(new int(1), [](int *p) {
            cout << "call lambda1 delete p2" << endl;
            delete p;
        });
        std::shared_ptr<int> p3(new int[10], [](int *p) {
            cout << "call lambda2 delete p3" << endl;
            delete []p;
        });
        std::shared_ptr<int> p4;
        p4.reset(new int(1), [](int* ptr) {
            std::cout << "use reset init and call lambda3 delete p4" << *ptr << std::endl;
            delete ptr;
        });
        return 0;
    }
    
    /******************************************************************
    use reset init and call lambda3 delete p41
    call lambda2 delete p3
    call lambda1 delete p2
    call Deleter delete p1
     *****************************************************************/
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小马敲马

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

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

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

打赏作者

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

抵扣说明:

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

余额充值