第21课 shared_ptr共享型智能指针

一. shared_ptr的基本用法

(一)与unique_ptr的比较


比较


shared_ptr


unique_ptr


备注


初始化


①shared_ptr<T> sp;

sp.reset(new T());

②shared_ptr<T> sp(new T());

③shared_ptr<T> sp1 = sp; //拷贝构造

④auto sp = make_shared<int>(10);


①unique_ptr<T> up;

up.reset(new T());

②unique_ptr<T> up(new T());

③unique_ptr<T> up1 = std::move(up);//移动构造

④auto up = make_unique<int>(10);


两者的构造函数将声明为explicit,即不允许隐式类型转换,如shared_ptr<int> sp = new int(10);


条件判断


如,if(sp){…}


如,if(up){…}


两都均重载operator bool()


解引用


*sp


*up


解引用,获得它所指向的对象


->mem


sp->mem


up->mem


重载->运算符


get()


sp.get()


up.get()


返回智能指针中保存的裸指针,要小心使用。


p.swap(q)


sp.swap(q);


up.swap(q);


交换p和q指针


独有操作


①shared_ptr<T> p(q);//拷贝构造

②p = q;//赋值

③p.unique();若p.use_count()为1,返回true,否则返回false。

④p.use_count()//返回强引用计数


①up=nullptr;释放up指向的对象,并将up置空。

②up.release();//up放弃对指针的控制权,返回裸指针,并将up置空

③up.reset();释放up指向的对象。

up.reset(q);其中q为裸指针。令up指向q所指对象。

up.reset(nullptr);置空


注意:

①unique_ptr不可拷贝和赋值,但可以被移动

②release会切断unique_ptr和它原来管理的对象间的联系。通常用来初始化另一个智能指针

(二)指定删除器

  1. shared_ptr<T> sp1(q, deleter1);与unique_ptr不同,删除器不是shared_ptr类型的组成部分。假设,shared_ptr<T> sp2(q,deleter2),尽管sp1和sp2有着不同的删除器,但两者的类型是一致的,都可以被放入vector<shared_ptr<T>>类型的同一容器里。

  2. 与std::unique_ptr不同,自定义删除器不会改变std::shared_ptr的大小。其始终是祼指针大小的两倍

  3. 当使用shared_ptr管理动态数组时,需要指定删除器。因为默认删除器不支持数组对象。如shared_ptr<int> sp(new int[10], std::default_delete<int[]>);

  4. 删除器可以是普通函数、函数对象和lambda表达式等。默认的删除器为std::default_delete,其内部是通过delete来实现功能的。

二. 剖析std::shared_ptr

(一)std::shared_ptr的内存模型

  1. shared_ptr包含了一个指向对象的指针和一个指向控制块的指针。每一个由std::shared_ptr管理的对象都有一个控制块,它除了包含引用计数之外,还包含了自定义删除器的副本和分配器的副本以及其他附加数据

  2. 控制块的创建规则:

  (1)std::make_shared总是创建一个控制块

  (2)从具备所有权的指针出发构造一个std::shared_ptr时,会创建一个控制块。(如std::unique_ptr转为shared_ptr时会创建控制块,因为unique_ptr本身不使用控制块,同时unique_ptr置空)

  (3)当std::shared_ptr构造函数使用裸指针作为实参时,会创建一个控制块。这意味从同一个裸指针出发来构造不止一个std::shared_ptr时会创建多重的控制块,也意味着对象会被析构多次。如果想从一个己经拥有控制块的对象出发创建一个std::shared_ptr,可以传递一个shared_ptr或weak_ptr而非裸指针作为构造函数的实参,这样则不会创建新的控制块。

【经验】

  ①尽可能避免将裸指针传递给一个std::shared_ptr的构造函数,常用的替代手法是使用std::make_shared。

  ②如果必须将一个裸指针传递给shared_ptr的构造函数,就直接传递new运算符的结果,而非传递一个裸指针变量。如shared_ptr<Widget> spw (new Widget, logginDel);

  ③不要将this指针返回给shared_ptr。当希望将this指针托管给shared_ptr时,类需要继承自std::enable_shared_from_this,并且从shared_from_this()中获得shared_ptr指针。(具体见《enable_shared_from_this》部分的分析)

  3. 引用计数(强引用计数)

  (1)shared_ptr的构造函数会使该引用计数递增,而析构函数会使该计数递减。但移动构造时表示从一个己有的shared_ptr移动构造到一个新的shared_ptr。这意味着一旦新的shared_ptr产生后,原有的shared_ptr会被置空,其结果是引用计数没有变化。

  (2)复制赋值同时执行两种操作(如sp1 和sp2是指向不同对象的shared_ptr,则sp1 = sp2时,将修改sp1使得其指向sp2所指的对象。而最初sp1所指向的对象的引用计数递减,同时sp2所指向的对象引用计数递增)

  (3)reset函数,如果不带参数时,则引用计数减1。如果不带参数时,如sp.reset(p)则sp原来指向的对象引用计数减1,同时sp指向新的对象(p)

  (4)如果实施一次递减后最后的引用计数变成0,即不再有shared_ptr指向该对象,则会被shared_ptr析构掉

  (5)引用计数的递增和递减是原子操作,即允许不同线程并发改变引用计数。

【编程实验】shared_ptr的陷阱分析

#include <iostream>
#include <vector>
#include <memory> // for smart pointer

using namespace std;

class Widget{};

void func(shared_ptr<Widget> sp){}

int funcException() { /*throw 1;*/ return 0; } //假设该函数会抛出异常

void demo(shared_ptr<int> sp, int f)
{

}

int main()
{
    //1. 陷阱:用同一裸指针创建多个shared_ptr
    //1.1 错误做法
    auto pw = new Widget;
    std::shared_ptr<Widget> spw1(pw); //强引用计数为1,为pw创建一个控制块
    //std::shared_ptr<Widget> spw2(pw); //强引用计数为1,为pw创建另一个新的控制块,会导致多次析构

    auto sp = new Widget;
    func(shared_ptr<Widget>(sp)); //慎用裸指针,sp将在func结束后被释放!

    //1.2 正确做法
    std::shared_ptr<Widget> spw3(spw1); //ok,pw的强引用计数为2。使用与spw1同一个控制块。
    std::shared_ptr<Widget> spw4(new Widget); //将new的结果直接传递给shared_ptr
    std::shared_ptr<Widget> spw5 = std::make_shared<Widget>(); //强烈推荐的做法!

    //2. 陷阱:在函数实参中创建shared_ptr
    //2.1 shared_ptr与异常安全问题
    //由于参数的计算顺序因编译器和调用约定而异。假定按如下顺序计算
    //A.先前new int,然后funcException();
    //B.假设恰好此时funcException产生异常。
    //C.因异常出现shared_ptr还来不及创建,于是int内存泄露
    demo(shared_ptr<int>(new int(100)), funcException());

    //2.2 正确做法
    auto p1 = std::make_shared<int>(100);
    demo(p1, funcException());

    //3. 陷阱:shared_ptr的循环引用(应避免)(见第22课 weak_ptr)

    //4. 删除器
    auto deleter1 = [](Widget* pw) {cout << "deleter1"<< endl; delete pw; };
    auto deleter2 = [](Widget* pw) {cout << "deleter2"<< endl; delete pw; };

    std::shared_ptr<Widget> pw1(new Widget, deleter1);
    std::shared_ptr<Widget> pw2(new Widget, deleter2);

    std::shared_ptr<Widget> pw3(pw1);
    pw3.reset(new Widget); //deleter恢复为默认的std::default_delete

    vector<std::shared_ptr<Widget>> vecs;
    vecs.emplace_back(pw1);
    vecs.emplace_back(pw2); //pw1和pw2虽然有不同的删除器,但类型相同,可以放入同一容器内。

    //5. 其它
    //5.1 shared_ptr的大小
    cout << sizeof(spw1) << endl;//8
    cout << sizeof(pw1) << endl; //8
    //5.2 shared_ptr管理动态数组(建议用std::array、std::vector取代)
    std::shared_ptr<int> pArray1(new int[10], [](int* p) {delete[] p; }); //使用delete[]
    std::shared_ptr<int> pArray2(new int[10], std::default_delete<int[]>()); //使用default_delete<int[]>()
    //5.3 常见操作
    cout << pw1.use_count() << endl; //2

    if (pw1) //pw1.use_count >= 1 ?
    {
        cout << "pw1.use_count >= 1" << endl;
    }
    else
    {
        cout << "pw1.use_count == 0" << endl;
    }

    return 0;
}

四. enable_shared_from_this模板的分析

(一)模板分析(以boost::enable_shared_from_this为例)

template<class T> class enable_shared_from_this
{
protected:

    enable_shared_from_this() BOOST_NOEXCEPT
    {
    }

    enable_shared_from_this(enable_shared_from_this const &) BOOST_NOEXCEPT
    {
    }

    enable_shared_from_this & operator=(enable_shared_from_this const &) BOOST_NOEXCEPT
    {
        return *this;
    }

    ~enable_shared_from_this() BOOST_NOEXCEPT // ~weak_ptr<T> newer throws, so this call also must not throw
    {
    }

public:

    shared_ptr<T> shared_from_this()
    {
        shared_ptr<T> p( weak_this_ );
        BOOST_ASSERT( p.get() == this );
        return p;
    }

    shared_ptr<T const> shared_from_this() const
    {
        shared_ptr<T const> p( weak_this_ );
        BOOST_ASSERT( p.get() == this );
        return p;
    }

public: // actually private, but avoids compiler template friendship issues

    // Note: invoked automatically by shared_ptr; do not call
    template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const
    {
        if( weak_this_.expired() )
        {
            weak_this_ = shared_ptr<T>( *ppx, py );
        }
    }

private:

    mutable weak_ptr<T> weak_this_;
};

boost::enable_shared_from_this

     1. enable_shared_from_this模板类提供两个public属性的shared_from_this成员函数。这两个函数内部会通过weak_this_(weak_ptr类型)成员来创建shared_ptr。

  2. _internal_accept_owner函数不能手动调用,这个函数会被shared_ptr自动调用,该函数是用来初始化唯一的成员变量weak_this_

  3. 根据对象生成顺序,先初始化基类enable_shared_from_this,再初始化派生类对象本身。这时对象己经生成,但weak_this_成员还未被初始化,最后应通过shared_ptr<T> sp(new T())等方式调用shared_ptr构造函数(内部会调用_internal_accept_owner)来初始化weak_this_成员。而如果在调用shared_from_this函数之前weak_this_成员未被初始化,则会通过ASSERT报错提示。

(二)使用说明

  1. 基类必须为enable_shared_from_this<T>,其中T为派生类的类名。(这种方法叫奇妙递归模板模式)

   2. 通过调用shared_from_this()成员函数获得一个和this指针指向相同对象的shared_ptr。

  3. 从内部实现看,shared_from_this会查询当前对象的控制块,并创建一个指向该控制块的新shared_ptr。这样的设计就要求当前对象己有一个与其关联的控制块。为了实现这一点,就必须有一个己经存在指向当前对象的std::shared_ptr,如果不存在,则通常shared_from_this会抛出异常。

【编程实验】安全地从this指针创建shared_ptr

#include <iostream>
#include <vector>
#include <memory>

using namespace std;

//1. 从this指针创建shared_ptr
//1.1 错误的做法
class Test1
{
public:
    //析构函数
    ~Test1() { cout <<"~Test1()" << endl; }

    //获取指向当前对象的指针
    std::shared_ptr<Test1> getObject()
    {
        shared_ptr<Test1> pTest(this); //危险! 直接从this指针创建,会为this对象创建新的控制块!
                                       //从而可能导致this所指对象被多次析构
        return pTest;
    }
};

//1.2 正确的做法
class Test2 : public std::enable_shared_from_this<Test2> //继承! 注意Test2为基类的模板参数  (递归模板模式)
{
public:
    //析构函数
    ~Test2() { cout << "~Test2()" << endl; }

    std::shared_ptr<Test2> getObject()
    {
        return shared_from_this(); //调用enable_shared_from_this模板的成员函数,获取this对象的shared_ptr
    }
};

//2. shared_from_this函数的正确调用
//2.1 一般做法
class Test3 : public std::enable_shared_from_this<Test3>
{
public:
    //构造函数中不能使用shared_from_this
    Test3()
    {
        //std::shared_ptr<Test3> sp = shared_from_this(); //error,此时基类(enable_shared_from_this<Test3>)
                                                          //虽己构造完,但shared_ptr的构造函数还没被调用,weak_this_指针
                                                          //未被初始化,因此调用shared_from_this会抛出异常
    }

    //调用process之前,必须确保shared_ptr的构造函数己被执行(即weak_this_被初始化)
    void process()
    {
        std::shared_ptr<Test3> sp = shared_from_this();
    }
};

//2.2 改进做法:利用工厂函数来提供shared_ptr
class Test4 : public std::enable_shared_from_this<Test4>
{
    Test4() {}  //构造函数设为private
public:

    //提供工厂函数
    template<typename... Ts>
    static std::shared_ptr<Test4> create(Ts&& ... params)
    {
        std::shared_ptr<Test4> ret(new Test4(params...));
        return ret;
    }

    void process()
    {
        std::shared_ptr<Test4> sp = shared_from_this();
    }
};

//3. enable_shared_from_this的应用举例
class Widget;
std::vector<std::shared_ptr<Widget>> processWidgets; //记录己被处理过的Widgets

class Widget : public std::enable_shared_from_this<Widget> //需要从这里继承
{
public:
    void process()
    {
        //错误做法:直接将this传给shared_ptr<Widget>
        //processWidgets.emplace_back(this); //将处理完的Widget加入链表。
                                             //error,这种做法会本质上是用裸指针来创建shared_ptr,会为this对象创建
                                             //新的控制块。如果外部new Widget时,也将指针交给shared_ptr管理时,会出现为同
                                             //一个this对象创建多个控制块,从而造成this对象的多次析构!

        //正确做法:(为了确保shared_from_this在shared_ptr构造函数后被调用,可以采用工厂函数的方式来创建Widget,
        //具体见前面的例子)
        processWidgets.emplace_back(shared_from_this()); //将指向当前对象的shared_ptr加入到链表中
    }

    ~Widget() { cout <<"~Widget()" << endl; }
};

int main()
{
    //1.  从this指针创建shared_ptr
    //1.1 错误做法:对象被多次析构
    {
        //std::shared_ptr<Test1> pt1(new Test1());
        //std::shared_ptr<Test1> pt2 = pt1->getObject();
    }

    //1.2 正确做法
    {
        std::shared_ptr<Test2> pt1(new Test2());
        std::shared_ptr<Test2> pt2 = pt1->getObject();
    }

    //2. shared_from_this的正确调用
    {
        //2.1 错误方法:
        Test3 t;
        //t.process(); //错误,shared_ptr构造函数没有被执行

        Test3* pt = new Test3();
        //pt->process(); //错误,原因同上。
        delete pt;

        //正确做法
        std::shared_ptr<Test3> spt(new Test3); //shared_ptr构造被执行,weak_this_被正确初始化
        spt->process(); 

        //2.2 工厂方法提供shared_ptr,确保shared_ptr构造函数被执行!
        std::shared_ptr<Test4> spt2 = Test4::create();
        spt2->process();
    }

    //3. enable_shared_from_this的应用举例
    {
        std::shared_ptr<Widget> sp(new Widget);
        sp->process();
    }

    return 0;
}

原文地址:https://www.cnblogs.com/5iedu/p/11622401.html

时间: 2024-10-06 00:31:19

第21课 shared_ptr共享型智能指针的相关文章

第22课 weak_ptr弱引用智能指针

一. weak_ptr的概况 (一)weak_ptr的创建 1. 直接初始化:weak_ptr<T> wp(sp); //其中sp为shared_ptr类型 2. 赋值: wp1 = sp; //其中sp为shared_ptr类型 wp2 = wp1; //其中wp1为weak_ptr类型 (二)常用操作 1. use_count():获取当前控制块中资源的强引用计数. 2. expired():判断所观测的资源是否失效(即己经被释放),即use_count是否为0. (1)shared_pt

shared_ptr 和auto_ptr智能指针

shared_ptr:计数的智能指针 它是一个包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,可以被自由地拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0)它时才删除被包装的动态分配的对象.shared_ptr也可以安全地放到标准容器中,并弥补了auto_ptr因为转移语义而不能把指针作为STL容器元素的缺陷. 线程安全性: shared_ptr 本身不是 100% 线程安全的.它的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr

auto_ptr,shared_ptr 智能指针的使用

Q: 那个auto_ptr是什么东东啊?为什么没有auto_array?A: 哦,auto_ptr是一个很简单的资源封装类,是在<memory>头文件中定义的.它使用“资源分配即初始化”技术来保证资源在发生异常时也能被安全释放(“exception safety”).一个auto_ptr封装了一个指针,也可以被当作指针来使用.当其生命周期到了尽头,auto_ptr会自动释放指针.例如: #include<memory> using namespace std;  struct X

C++11 shared_ptr 智能指针 的使用,避免内存泄露

多线程程序经常会遇到在某个线程A创建了一个对象,这个对象需要在线程B使用, 在没有shared_ptr时,因为线程A,B结束时间不确定,即在A或B线程先释放这个对象都有可能造成另一个线程崩溃, 所以为了省时间一般都是任由这个内存泄漏发生. 当然也可以经过复杂的设计,由一个监控线程来统一删除, 但这样会增加代码量和复杂度.这下好了,shared_ptr 可以方便的解决问题,因为它是引用计数和线程安全的. shared_ptr不用手动去释放资源,它会智能地在合适的时候去自动释放. 我们来测试看看效果

第61课 智能指针类模板

1. 智能指针的意义 (1)现代C++开发库中最重要的类模板之一 (2)C++中自动内存管理的主要手段 (3)能够在很大程度上避开内存相关的问题(如内存泄漏.内存的多次释放等) 2. STL中的智能指针 (1)auto_ptr智能指针 ①生命周期结束时,销毁指向的内存空间 ②只能用来管理单个动态创建的对象,而不能管理动态创建的数组.即不能指向堆数组,只能指针堆对象(变量) int* pn = new int[100]; auto_ptr<int> ap(pn); //auto_ptr指向堆数组

智能指针 shared_ptr 解析

最近正在进行<Effective C++>的第二遍阅读,书里面多个条款涉及到了shared_ptr智能指针,介绍的太分散,学习起来麻烦,写篇blog整理一下. LinJM   @HQU shared_ptr是一个智能指针.在C++ 11颁布之前,它包含在TR1(Technical Report 1)当中,现在囊括在C++11的标准库中. 智能指针 智能指针(Smart pointers)是存储"指向动态分配(在堆上)的对象的指针"的对象.也就是说,智能指针其实是个对象.不过

C++ 智能指针(shared_ptr/weak_ptr)源码分析

C++11目前已经引入了unique_ptr, shared_ptr, weak_ptr等智能指针以及相关的模板类enable_shared_from_this等.shared_ptr实现了C++中的RAII机制,它不仅仅具有一般指针(build-in/raw)的特性,更重要的是它可以自动管理用户在堆上创建的对象的生命周期,让用户不用负责内存回收,避免内存泄漏.一般的智能指针都定义为一个模板类,它的类型由被管理的对象类型初始化,内部包含了指向该对象的指针以及指向辅助生命周期管理的管理对象的指针.

智能指针(二):shared_ptr实现原理

前面讲到auto_ptr有个很大的缺陷就是所有权的转移,就是一个对象的内存块只能被一个智能指针对象所拥有.但我们有些时候希望共用那个内存块.于是C++ 11标准中有了shared_ptr这样的智能指针,顾名思义,有个shared表明共享嘛.所以shared_ptr类型的智能指针可以做为STL容器的元素 下面我们来瞧瞧shared_ptr具体是咋实现的.相较auto_ptr有下面几个不同的地方: 1.引进了一个计数器shared_count,用来表示当前有多少个智能指针对象共享指针指向的内存块 2

动态内存1(动态内存与智能指针)

静态内存用来保存局部 static 对象.类 static 数据成员 以及任何定义在函数之外的变量.栈内存用来存储定义在函数内部的非 static 对象.分配在静态或栈内存中的对象由编译器自动创建和销毁.对于栈对象,仅在其定义的程序块运行时才存在:static 对象在使用之前分配,在程序结束时销毁. 除了静态内存和栈内存,每个程序还拥有一个内存池.这部分内存被称作自由空间或堆.程序用堆来存储动态分配的对象--即,那些在程序运行时分配的对象.动态对象的生存周期由程序来控制,即当动态对象不再使用时,