C++(标准库):04---智能指针之shared_ptr

一、shared_ptr类

  • 头文件:#include<memory>
  • 智能指针,是一个模板。创建智能指针时,必须提供指针所指的类型
  • 如果当做前提条件判断,则是检测其是否为空
shared_ptr<string> p1;   //指向string
shared_ptr<list<int>> p2;//指向int的list

if(p1 && p1->empty())
    *p1="h1";

二、shared_ptr类的操作

shared_ptr类的默认初始化:

  • 如果不初始化一个shared_ptr类对象,那么该对象默认初始化为一个空指针

三、make_shared函数

  • 最安全的分配和使用动态内存的方法就是调用该函数
  • 此函数在内存中动态分配对象并初始化,返回此对象的shared_ptr
//指向一个值为42的int的shared_ptr
shared_ptr<int> p = make_shared<int>(42);

//p2指向一个值为10个'9'的string
shared_ptr<string> p2=make_shared<string>(10, '9');

//p3指向一个值初始化为0的int数
shared_ptr<int> p3 = make_shared<int>();
  • 配合auto使用:make_shared函数可以赋值给auto,这样比较简单
auto p=make_shared<vector<string>>();

四、shared_ptr的拷贝、赋值与引用计数

  • 引用计数:shared_ptr类所指向的对象都有一个引用计数
  • 对shared_ptr类进行拷贝时,计数器就会增加。例如:当用一个shared_ptr初始化另一个shared_ptr、或者它作为参数传递给一个函数以及作为函数的返回值,它所关联的计数器就会增加
  • 当我们给让shared_ptr指向另一个对象或者shared_ptr销毁时,原对象的计数器就会递减
  • 一旦一个shared_ptr的计数器为0,就会自动释放该对象的内存
auto p=make_shared<int>(42); //p指向一个引用者
auto q(p); //用p初始化q,那么p所指的对象计数器加1
auto r=make_shared<int>(42);
r=q;
  • 将q赋值给r,那么:
    •  r原来所指的对象引用计数变为0,然后自动释放内存
    • q所指的对象的引用计数+1

五、shared_ptr的自动销毁对象内存机制

  • 由上面可知,当指向一个对象的最后一个shared_ptr对象被销毁时,shared_ptr类会自动销毁此对象。shared_ptr类是通过析构函数来完成销毁工作的
  • 内存浪费:因为只有在销毁掉最后一个shared_ptr时,该指针所指向的内存才会释放,因此如果你忘记了销毁程序不再需要的shared_ptr,程序仍然正在执行,那么就造成内存浪费

六、shared_ptr与作用域的关系

  • shared_ptr类所指向的内存何时被释放,与shared_ptr类的生存周期有关

演示案例:

  • 首先我们定义下面的函数返回一个指向于一个值的share_ptr指针
shared_ptr<Foo> factory(T arg)
{
    return make_share<Foo>(arg);//返回一个share_ptr类型的智能指针
}
  • 情景一:例如下面函数调用factory函数来生成一个shared_ptr指针,但是p一旦离开了作用域(use_factory函数),那么p指针就失效了,因此p所指向的内存地址也就自动释放了
//函数结束之后,p就自动释放它所指向的对象的内存
void use_factory(T arg)
{
    shared_ptr<Foo>  p=factory(arg);
}
  • 情景二:下面的函数也是 factory函数来生成一个shared_ptr指针,但是p指针通过返回值返回了,所以,如果有另一个shared_ptr指针调用了该函数,那么该p所指向的内存地址不会随着use_factory函数的调用而释放
auto use_factory(T arg)
{
    shared_ptr<Foo> p=factory(arg);

    return p;
}

七、shared_ptr与new的使用

使用规则:

  • ①我们可以使用将shared_ptr类对象指向一个new所申请的动态内存
  • ②new申请的动态内存的使用、释放等规则仍然符合shared_ptr类的使用规则

使用语法:

  • 因为智能指针的构造函数是explicit的因此:我们不能将一个内置指针隐式地转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针
shared_ptr<int> p=new int(1024);   //错误

shared_ptr<int> p2(new int(1024)); //正确:使用直接初始化
  • 动态内存作为返回值时的使用手法:限于上面的使用语法,一个返回shared_ptr的函数不能在其返回语句中隐式转换为一个普通指针 
shared_ptr<int> clone(int p)
{
    return new int(p); //错误
}

shared_ptr<int> clone(int p)
{
    return shared_ptr<int>(new int(p)); //正确
}

shared_ptr类的其他使用语法:

八、shared_ptr类的函数传参使用

  • 当一个函数的参数是shared_ptr类时,有以下规则:
    • 函数的调用是传值调用
    • 调用函数时,该shared_ptr类所指向的对象引用计数加1。但是函数调用完成之后,shared_ptr类自动释放,对象的引用计数又减1
void process(shared_ptr<int> ptr){ ... }

shared_ptr<int> p(new int(42)); //初始化一个智能指针对象p
process(p);  //p所指的对象引用计数加1
//process函数调用之后,p所指的引用计数减1
int i=*p; //正确

函数参数使用时与new的关系:

  • 因为shared_ptr类会在生存周期结束之后,将引用计数减1,当引用计数为0时,会释放内存空间
  •  下面是一个特殊的应用场景,需要注意
void process(shared_ptr<int> ptr){ ... }

int *x(new int(1024));
process(x);  //错误,不能将int*转换为一个shared_ptr<int>
process(shared_ptr<int>(x)); //合法的,但是process函数返回之后内存会被释放
int j=*x; //错误,x所指的内存已经被释放了

九、get函数的使用

  • shared_prt类的get函数返回一个内置指针,指向智能指针所管理的对象
  • 此函数的设计情况:我们需要向不能使用智能指针的代码传递一个内置指针
  • get函数将内存的访问权限传递给一个指针,但是之后代码不会delete该内存的情况下,对get函数的使用才是最安全的
  • 永远不要用get初始化另一个智能指针或者为另一个智能指针赋值
shared_ptr<int> p(new int(42));  //引用计数变为1
int *q=p.get();  //正确:使用q需要注意,不要让它管理的指针被释放

{//新语句块
    shared_ptr<int>(q); //用q初始化一个智能指针对象
} //语句块结束之后,智能指针对象释放它所指的内存空间

int foo=*p;//错误的,p所指的内存已经被释放了

十、reset、unique函数的使用

  • reset函数会将shared_prt类原先所指的内存对象引用计数减1并且指向于一块新的内存
shared_ptr<int> p;

p=new int(1024);  //错误:不能将一个指针赋予shared_ptr
p=reset(new int(1034)); //正确,p指向一个新对象
  • reset函数与unqie函数配合使用:在改变对象之前,检查自己是否为当前对象的唯一用户
shared_ptr<string> p=make_shared<string>("Hello");

if(!p.unique()) //p所指向的对象还有别的智能指针所指
    p.reset(new string(*p)); //现在可以放心的改变p了

*p+=newVal; //p所指向的对象只有自己一个智能指针,现在可以放心的改变对象的值了

十一、异常处理

  • 当程序发生异常时,我们可以捕获异常来将资源被正确的释放。但是如果没有对异常进行处理,则有以下规则:
    • shared_ptr的异常处理:如果程序发生异常,并且过早的结束了,那么智能指针也能确保在内存不再需要时将其释放
    • new的异常处理:如果释放内存在异常终止之后,那么就造成内存浪费
voif func()
{
    shared_ptr<int> sp(new int(42));
    ...//此时抛出异常,未捕获,函数终止
}//shared_ptr仍然会自动释放内存
voif func()
{
    int *ip=new int(42);
    ...//此时抛出异常,未捕获
    delete ip; //在退出之前释放内存,此语句没有执行到,导致内存浪费
}

十二、重置shared_prt类删除器

  • 概念:前面介绍过,当shared_ptr生命周期结束时,会调用默认的析构函数来释放(delete)自己所指向的内存空间。但是我们可以使用shared_prt的语法来指定删除器函数,那么在shared_ptr生命周期结束时就会自动调用这个函数
  • 关于删除器的一个效率问题可以参阅此篇文章中的“效率与灵活性”:https://blog.csdn.net/qq_41453285/article/details/92403438

演示案例:

  • 下面演示一个shared_ptr指定删除器函数以及避免内存泄露的案例
  • 错误情景:我们调用f函数来打开一个网络连接,但是在f函数调用之后没有关闭这个连接。因此就会造成内存的泄露 
struct destination;    //连接的对象
struct connection;     //连接需要的信息
connection connect(destbination*); //打开连接
void disconnect(connection);       //关闭连接
void f(destination &d)
{
    connection c=connect(&d);//打开一个连接
    ....//使用这个连接
    
    //如果在f函数退出之前忘记调用disconnect函数,那么该连接就没有关闭
}
  • 正确情景:现在我们定义一个新的函数“end_connection”,并且配合shared_ptr类的使用。shared_ptr指定了一个删除器函数“end_connection”。因此下面的代码能够保证在f函数的各种调用结束时,保证连接正确的关闭
void end_connection(connection *p)
{
    disconnection (*p);
}

void f(destination &d)
{
    connection c=connect(&d);
    shared_ptr<connection> p(&c,end_connection);
    ....//使用这个连接
    
    //当f函数退出或者异常退出,p都会调用end_connection函数
}

十三、shared_prt与动态数组的使用

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
shared_ptrC++中的智能指针,它采用引用计数的方法来实现释放指针所指向的资源。当使用shared_ptr时,它会记录有多少个shared_ptr指向同一个对象,只有当最后一个shared_ptr被销毁时,该对象的内存才会被释放。因此,shared_ptr可以自动管理内存,不需要手动释放。 在代码中,使用shared_ptr可以像普通指针一样操作对象。当需要创建一个shared_ptr对象时,可以使用std::make_shared函数来构造,如下所示: ``` std::shared_ptr<int> sharedPtr1 = std::make_shared<int>(10); ``` 可以通过将shared_ptr赋值给其他shared_ptr来共享资源,如下所示: ``` std::shared_ptr<int> sharedPtr2 = sharedPtr1; ``` 当所有的shared_ptr都被销毁时,内存会自动释放。可以使用use_count()函数获取shared_ptr的引用计数,如下所示: ``` std::cout << "sharedPtr2 use count: " << sharedPtr2.use_count() << std::endl; ``` 当引用计数为0时,内存会被自动释放。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C++智能指针shared_ptr分析](https://download.csdn.net/download/weixin_38705004/13788082)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [C++11中的智能指针unique_ptrshared_ptr和weak_ptr详解](https://blog.csdn.net/chenlycly/article/details/130918547)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

董哥的黑板报

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

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

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

打赏作者

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

抵扣说明:

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

余额充值