16.2 C++智能指针-shared_ptr

16.1 C++智能指针-new/delete探秘
16.2 C++智能指针-shared_ptr
16.3 C++智能指针-weak_ptr
16.4 C++智能指针-shared_ptr使用场景、陷阱、性能分析与使用建议
16.5 C++智能指针-unique_ptr

    C++标准库中有4种智能指针,即std::auto_ptr、std::unique_ptr、std::shared_ptr、std::weak_ptr
    shared_ptr是共享式指针的概念。多个指针指向同一个对象,最后一个指针被销毁时,这个对象就会被释放。
    weak_ptr这个智能指针是用来辅助shared_ptr工作的,后面都会详细介绍。
    unique_ptr是一种独占式指针的概念,同一个时间内只有一个指针能够指向该对象,当然,该对象的拥有权(所有权)是可以移交出去的。

2.shared_ptr

    shared_ptr指针采用的是共享所有权来管理所指向对象的生存期。
    shared_ptr的工作机制是使用引用计数,每一个shared_ptr指向相同的对象(内存),所以很显然,只有最后一个指向该对象的shared_ptr指针不需要再指向该对象时,这个shared_ptr才会去析构所指向的对象。
    智能指针是一个类模板,需要用到尖括号“<>”。“<>”中是指针可以指向的类型,后面跟智能指针名字。看一看使用shared_ptr的一般形式:

  2.1 shared_ptr基础

(1)常规初始化(shared_ptr和new配合使用)

    shared_ptr<int> pi(new int(100)); //pi指向一个值为100的int型数据
    shared_ptr<int> pi2 = new int(100); //这个写法不行,智能指针是explicit,是不可以进行隐式类型转换的,必须用直接初始化形式。而带等号一般都表示要隐式类型转换的 

    shared_ptr<int> makes(int value)
    {
        return new int(value); //不可以,因为无法把new得到的int转换成shared_ptr
    }
    shared_ptr<int> makes(int value)
    {
        return shared_ptr<int>(new int(value)); //可以,显式用int创建shared_ptr<int>
    }

    裸指针可以初始化shared_ptr但这是一种不被推荐的用法,智能指针和裸指针不要穿插使用,一不小心容易出问题,尽量采用后续要讲解到的make_shared

{
    int* pi = new int;
    shared_ptr<int> p1(pi);    //裸指针可以初始化shared_ptr 但这是一种不被推荐的用法 智能指针和裸指针不要穿插使用,一不小心容易出问题,尽量采用后续要讲解到的make_shared
    //上面的写法并不推荐,虽然内存也能够被正常释放,但即便用裸指针,也应该直接传递new运算符而不是传递一个裸指针变量。修改为如下,原因后续讲解陷阱时再细谈
    shared_ptr<int> p1(new int);    
}

(2)make_shared函数(shared_ptr和new配合使用)

    这是一个标准库里的函数模板,被认为是最安全和更高效的分配和使用shared_ptr智能指针的一个函数模板。
    它能够在动态内存(堆)中分配并初始化一个对象,然后返回指向此对象的shared_ptr。看看如下范例:

{
    shared_ptr<int> p2 = std::make_shared<int>(100); //这个shared_ptr指向一个值为100的整型的内存 ,类似int *pi = new int(100);
    shared_ptr<string> p3 = std::make_shared<string>(5, 'a'); //5个字符a,类似于string mystr(5, 'a');,注意到,make_shared后圆括号里的参数的形式取决于<>中的类型名,此时这些参数必须和string里的某个构造函数匹配
    
    shared_ptr<int> p4 = make_shared<int>(); //p4指向一个int,int里保存的值是0;这个就是值初始化
    p4 = make_shared<int>(400); //p4释放刚才的对象,重新指向新对象

    auto p5 = std::make_shared<string>(5, 'a'); //用auto保存make_shared结果,写法上比较简单
}

  2.2 shared_ptr引用计数的增加和减少

    shared_ptr是共享式指针,使用引用计数,每一个shared_ptr的复制都指向相同的对象(内存),只有最后一个指向该对象的shared_ptr指针不需要再指向该对象时,这个shared_ptr才会去析构所指向的对象(释放内存)

(1)引用计数的增加。

    auto p6 = std::make_shared<int>(100); //目前p6所指的对象只有p6一个引用者
    auto p7(p6);  //写成auto p7 = p6;也可以,智能指针拷贝,p7和p6指向相同的对象,此对象目前有两个引用者   
    myfunc(p7); //当然,这个函数执行完毕后,这个指针的引用计数会恢复

    1>像上面的代码这样,用p6来初始化p7智能指针,就会导致所有指向该对象(内存)的shared_ptr引用计数全部增加1。
    2>把智能指针当成实参往函数里传递。

void myfunc(shared_ptr<int> ptmp) //如果传递引用作为形参进来,则引用计数不会增加
{
    return;
}
myfunc(p7); //当然,这个函数执行完毕后,这个指针的引用计数会恢复

    3>作为函数的返回值。

shared_ptr<int> myfunc2(shared_ptr<int>& ptmp) //这里是引用,所以计数还是为
{
    return ptmp;
}
auto p8 = myfunc2(p7); //如果有p8接收myfunc2函数返回值,那么此时引用计数会变成3
//myfunc(p7); //引用计数保持3不变,因为没有变量来接收myfunc(p7)调用的返回值

(2)引用计数的减少

    1>给shared_ptr赋一个新值,让该shared_ptr指向一个新对象。

p8 = std::make_shared<int>(200); //p8指向新对象计数1,p6,p7计数恢复为2
p7 = std::make_shared<int>(200); //p7指向新对象计数1,p6指向的原对象恢复计数为1
p6 = std::make_shared<int>(200); //p6指向新对象计数1,p6指向的原对象内存被释放

    2>局部的shared_ptr离开其作用域

auto p6 = std::make_shared<int>(100); //目前p6所指的对象只有p6一个引用者
auto p7(p6);  //写成auto p7 = p6;也可以,智能指针拷贝,p7和p6指向相同的对象,此对象目前有两个引用者   
myfunc(p7); //当然,这个函数执行完毕后,这个指针的引用计数会恢复

    3>当一个shared_ptr引用计数变为0,它会自动释放自己所管理的对象

auto p9 = std::make_shared<int>(100); //只有p9指向该对象
auto p10 = std::make_shared<int>(100);
p9 = p10; //给p9赋值让p9指向p10所指向的对象,该对象引用计数为2;而原来p9指向的对象引用计数会变成0,所以会被自动释放

运行过程:
在这里插入图片描述

  2.3 shared_ptr指针常用操作

(1)use_count成员函数

    该成员函数用于返回多少个智能指针指向某个对象。该成员函数主要用于调试目的,效率可能不高。

{
    shared_ptr<int> myp(new int(100));
    int icount = myp.use_count(); //1
    shared_ptr<int> myp2(myp);
    icount = myp.use_count(); //2
    shared_ptr<int> myp3;
    myp3 = myp2;
    icount = myp.use_count(); //3
    icount = myp3.use_count(); //3
}

(2)unique成员函数

    是否该智能指针独占某个指向的对象,也就是若只有一个智能指针指向某个对象,则unique返回true,否则返回false

{
    shared_ptr<int> myp(new int(100));
    if (myp.unique()) //本条件成立
    {
    //"myp"独占所指向的对象
        cout << "myp unique ok" << endl;
    }
    shared_ptr<int> myp2(myp);
    if (myp.unique()) //本条件不再成立
    {
        cout << "myp unique ok" << endl;
    }
}

在这里插入图片描述

(3)reset成员函数

    若pi是唯一指向该对象的指针,则释放pi指向的对象,将pi置空。
    若pi不是唯一指向该对象的指针,则不释放pi指向的对象,但指向该对象引用计数会减1,同时将pi置空。

{
    shared_ptr<int> pi(new int(100));
    pi.reset(); //释放pi指向的对象,将pi置空
    if (pi == nullptr)//条件成立
    {
    cout << "pi被置空" << endl; 
    }
}
{
    shared_ptr<int> pi(new int(100));
    auto pi2(pi); //pi2引用计数现在为2
    pi.reset(); //pi被置空,pi2引用计数变为1

}

    若pi是唯一指向该对象的指针,则释放pi指向的对象,让pi指向新内存。
    若pi不是唯一指向该对象的指针,则不释放pi指向对象,但指向该对象的引用计数会减1,同时让pi指向新内存。

{
    shared_ptr<int> pi(new int(100));
    pi.reset(new int(1)); //释放原内存(内容为100的内存),指向新内存(内容为1的内存)
    cout << "断点调试" << endl;
}
{
    shared_ptr<int> pi(new int(100));
    auto pi2(pi);  //pi2引用计数为2
    pi.reset(new int(1));  //现在pi引用计数为1,上面的pi2引用计数为1
    if (pi.unique()) //本条件成立
    {
        cout << "pi unique ok" << endl;
    }
    if (pi2.unique()) //本条件成立
    {
        cout << "pi2 unique ok" << endl;
    }
}

  空指针也可以通过reset来重新初始化。

{
    shared_ptr<int> p; //p现在是空指针
    p.reset(new int(100)); //释放pi指向的对象,让pi指向新内存,因为原来pi为空,所以就等于啥也没释放        
}

在这里插入图片描述

(4)*解引用

  解引用的感觉,获得p指向的对象。

{
    shared_ptr<int> pother(new int(12345));
    char outbuf[1024];
    sprintf_s(outbuf, sizeof(outbuf), "%d", *pother); //outbuf中的内容就是"12345",pother不发生任何变化,引用计数仍旧为1
    //OutputDebugStringA(outbuf); //在MyProjectMFC工程中使用F5运行,执行到这行时可以在“输出”窗口中打印出outbuf中的内容,但在MyProject工程中,这行无法编译通过        
}

(5)get成员函数

  p.get():返回p中保存的指针。小心使用,若智能指针释放了所指向的对象,则返回的这个指针所指向的对象也就变得无效了。

{
    shared_ptr<int> myp(new int(100));
    int* p = myp.get();        
    //myp.reset(); //不可,结果无法预料
    *p = 45;        
    //delete p; //不可,结果无法预料        
}

(6)swap成员函数

  用于交换两个智能指针所指向的对象。当然,因为是交换,所以引用计数并不发生变化

{
    shared_ptr<string> ps1(new string("I Love China1!"));
    shared_ptr<string> ps2(new string("I Love China2!"));
    std::swap(ps1, ps2); //可以
    ps1.swap(ps2); //也可以
}

(7)=nullptr;

  将所指向对象的引用计数减1,若引用计数变为0,则释放智能指针所指向的对象。
  将智能指针置空

{
    shared_ptr<string> ps1(new string("I Love China!"));
    ps1 = nullptr;
    cout << "断点调试" << endl;
}

(8)智能指针名字作为判断条件;

{
    shared_ptr<string> ps1(new string("I Love China1!"));
    //若ps1指向一个对象,则条件成立
    if (ps1)//条件成立
    {            
        cout << "ps1" << endl; //执行
    }
    cout << "断点调试" << endl;
}

在这里插入图片描述
``

(9)指定删除器和数组问题

  指定删除器
    智能指针能在一定的时机自动删除它所指向的对象。那么,它是怎样自动删除所指向的对象呢?这其实比较好想象,delete它所指向的对象就应该可以了。默认情况下,shared_ptr正是使用delete运算符作为默认的删除它所指向的对象的方式。
    同时,程序员可以指定自己的删除器,这样当智能指针需要删除一个它所指向的对象时,它不再去调用默认的delete运算符来删除对象,而是调用程序员为它提供的删除器来删除它所指向的对象。
    shared_ptr指定删除器的方法比较简单,一般只需要在参数中添加具体的删除器函数名即可(注意,删除器是一个单形参的函数)。

void myDeleter(int* p) //自己的删除器,删除整型指针用的,当p的引用计数为0,则自动调用这个删除器删除对象,释放内存
{
    delete p;
}
{
    shared_ptr<int> p(new int(12345), myDeleter); //指定删除器
    shared_ptr<int> p2(p); //现在两个引用计数指向该对象
    p2.reset(); //现在一个引用计数指向该对象,p2为nullptr了
    p.reset();//此时只有一个指针指向该对象,所以释放指向的对象,调用自己的删除器myDeleter,同时p置空
}

    删除器也可以是一个lambda表达式,lambda表达式后面章节(20.8节)会讲,暂时可以把lambda表达式理解成一个匿名函数(是一个表达式,但使用起来也比较像一个匿名函数)。注意,用{}包着的部分都是lambda表达式的组成部分,直接作为一个参数来用

{
    shared_ptr<int> p(new int(12345), [](int* p) {
        delete p;
    });
    p.reset(); //会调用删除器(lambda表达式)
}

    默认的删除器工作的是挺好,但是有一些情况需要自己指定删除器,因为默认的删除器处理不了——用shared_ptr管理动态数组的时候,就需要指定自己的删除器,默认的删除器不支持数组对象。

{
    shared_ptr<int[]> p(new int[10], [](int* p) {
        delete[] p; 
    });
    p.reset();
}
class A
{
public:
    A()
    {
        cout << "A()构造函数被调用" << endl;
    }
    ~A()
    {
        cout << "~A()析构函数被调用" << endl;
    }
};
{
    //shared_ptr<A> pA(new A[10]); //异常,因为系统释放pA是使用delete pA而不是使用delete[]pA,所以必须自己写删除器
    shared_ptr<A> pA(new A[10], [](A* p) { 
    	delete[] p; 
    	}); //一切正常
}
{
    shared_ptr<A> pA(new A[10], std::default_delete<A[]>());
}

    当遇到数组的时候,程序员做的很多工作都是为了保证数组能够正常释放。其实,在定义的时候如果像下面这样,即使不写自己的删除器,也能正常释放内存:


{
    shared_ptr<A[]> pA(new A[10]);  //<>中加个[]就行了
    shared_ptr<int[]> p(new int[10]); //<>中加个[]就行了,而且加了[]后,引用也方便比如p[0]、p[1]......p[9]直接拿来用
}

    自己写一个函数模板来封装shared_ptr数组,也是可以的

template<typename T>
shared_ptr<T> make_shared_array(size_t size)
{
    return shared_ptr<T>(new T[size], default_delete<T[]>()); //指定了删除器
}
//在main主函数中,加入如下代码:
{
	shared_ptr<int> pintArr = make_shared_array<int>(5); //末尾数字代表数组元素个数
	shared_ptr<A> pAArr = make_shared_array<A>(15); //末尾数字代表数组元素个数
}

  指定删除器的额外说明
    就算是两个shared_ptr指定的删除器不相同,只要它们所指向的对象相同,那么这两个shared_ptr也属于同一个类型:
在main主函数中,加入如下代码:

{
    auto lambda1 = [](int* p)
    {			
        delete p;
    };

    auto lambda2 = [](int* p)
    {		
        delete p;
    };

    shared_ptr<int> p1(new int(100), lambda1); //指定lambda1为删除器
    shared_ptr<int> p2(new int(200), lambda2); //指定lambda2为删除器
    p2 = p1; //p2会先调用lambda2把自己所指向对象释放,然后指向p1所指对象,现在该对象引用计数为2。整个main函数执行完毕之前还会调用lambda1释放p1,p2共同指向的对象
    vector<shared_ptr<int>> pvec{ p1,p2 }; //在.cpp源文件头#include <vector>
    cout << "断点调试" << endl;
}

    前面介绍过make_shared,make_shared是一种被提倡的生成shared_ptr的方法,但是如果使用make_shared方法生成shared_ptr对象,那就没有办法自定义删除器了。这一点从上面这些范例代码中能够看得出来。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值