shared_ptr的理解和注意事项

【C++】share_ptr详解 分析:根据对象生成顺序,先初始化基类 enable_shared_from_this ,再初始化派生类 Frame 对象本身,这时 Frame 对象已经生成,但 _M_weak_this 成员还未被初始化,最后应通过 shared_ptr<T> sp(new T()) 等方式调用 shared_ptr 构造函数(内部会调用 _M_weak_assign 成员函数来初始化 _M_weak_this 成员)。share_ptr 内部中的引用计数是线程安全的,但是引用的资源不是线程安全的。 阅读详情

昨天把shared_ptr的源码分析了一遍,也看了一些有关shared_ptr分析的文章,今天自己来总结一下。
 

以独立语句将newed对象置入智能指针

对于下面这样一个使用智能指针的函数调用,可能会造成内存泄漏:

process(std::shared_ptr<int>(new int(10)), priority());

编译器没有规定传入参数的调用顺序,可能先执行new表达式,然后执行priority()函数,最后构造shared_ptr对象。如果priority()函数中抛出异常,那么new出来的内存将得不到释放。这会造成内存泄漏。

所以要使用下面两种方式:
方式一:

shared_ptr<int> p(new int(10));
process(p, priority());

方式二:

process(std::make_shared<int>(10), priority);

前者即是以独立语句将new分配过的对象置入只能指针,后者是利用工厂函数,直接生成只能指针。

小知识:make_shared()和普通shared_ptr(new xx)构造方法的区别

make_shared的效率更高。直接使用shared_ptr(new xx)底层实际上涉及两次内存分配,一次是为被管理的对象分配内存,一次是为ref_count控制块分配内存。而make_shared则是一次性分配好一大块内存来同时持有被管理的对象和ref_count控制块。增加了代码执行速度,且能避免一些控制块的薄记信息(内存cookie),潜在减少了程序占用的空间。不过make_shared有个缺点,使用make_shared会导致对象销毁和内存被回收之间可能会有延迟。因为使用make_shared相当于把被管理对象内存和ref_count控制块内存绑定在一起了,我们知道,ref_count控制块内存本来是由use_count和weak_count共同维护的,假设被管理对象引用计数为0了,但是仍然有weak_ptr存在,那么这一大块内存就不能销毁;而传统的shared_ptr(new xx)方式,当对象引用计数为0时,对象内存销毁。当weak_ptr引用计数为0时,ref_count控制块内存销毁,所有的内存都能够及时清理。

  • 相比于使用new,make函数可以消除代码重复,提高异常安全。而且std::make_shared生成的代码更小更快。不适用make函数的场合包括自定义删除器和想要传递大括号初始值(这个暂时不太懂)。使用make函数不明智的场合包括:(1)自定义内存管理函数的类(2)内存紧张的系统,有非常大的对象,然后std::weak_ptr比std::shared_ptr长寿。

注意循环引用带来的内存泄漏

循环引用就是指,两个shared_ptr,你持有我,我持有你,双方都认为对方死了自己才能销毁,结果两方都无法销毁,造成内存泄漏。

示例代码:

class B;

class A { 
public:
    A(){
        std::cout<<"A ctor"<<std::endl;
    }   
    ~A(){
        std::cout<<"A dtor"<<std::endl;
    }   
    void do_something() {
        if(b_.lock()){
            std::cout<<"lock success, still alive"<<std::endl; 
            std::cout<<"use_count="<<b_.use_count()<<std::endl;  //输出1
        }   
    }   
public:
    //std::shared_ptr<B> b_;
    std::weak_ptr<B> b_; 
};

class B { 
public:
    B(){
        std::cout<<"B ctor"<<std::endl;
    }   
    ~B(){
        std::cout<<"B dtor"<<std::endl;
    }
public:
    std::shared_ptr<A> a_;
};

int main()
{
    std::shared_ptr<A> pa(new A);
    std::shared_ptr<B> pb(new B);
    std::cout<<"pb->use_count"<<pb.use_count()<<std::endl;
    pa->b_ = pb;
    pb->a_ = pa;
    pa->do_something();

    std::weak_ptr<B> pbb(pb);
    std::cout<<(pbb.lock()).use_count()<<std::endl; //输出2

    return 0;

我们可以使用weak_ptr来打破这个局面,而不是手动打破循环。一方持有另一方的shared_ptr(强引用),而另外一方则持有对方的weak_ptr(弱引用)。与之前的区别是,持有弱引用的一方在使用weak_ptr时,需要使用lock()方法,提升成为shared_ptr,提升失败说明对方已死,提升成功便可正常使用。这样就可以打破循环引用,避免内存泄漏。

代码中要注意的一点是,weak_ptr.lock()方法虽然会提升引用计数,但是如果我们仅仅把.lock()用作表达式,那么实际上是临时对象temp存在的时候引用计数为2,临时对象在表达式结束后立即死亡,引用计数又回到1。所以,我们如果要使用weak_ptr提升成为shared_ptr,必须用一个shared_ptr来接收,否则weak_ptr不会提升成为shared_ptr。
 

类向外传递this与shared_ptr

当我们在一个类的成员函数中,想知道自己this所在的shared_ptr时,不可以用shared_ptr(this)直接构造,因为这样会构造一个独立的局部shared_ptr,该shared_ptr销毁时会释放掉this。如果外部不知道该对象已经销毁,再调用就会爆炸。所以应该用shared_from_this,所在类需要继承std::enable_shared_from_this。

示例代码:

class A : public std::enable_shared_from_this<A> {  //注意继承要加模板参数
public:
    A() { std::cout<<"ctor"<<std::endl; }
    ~A() { std::cout<<"dtor"<<std::endl; }
public:
    void func() {
        //std::shared_ptr<A> local_sp(this);   //错误用法
        //std::cout<<local_sp.use_count()<<std::endl;  //引用计数为1,因为这个局部的shared_ptr是用this单独生成的,和外部没有关系,所以会造成两次销毁doble delete
        std::shared_ptr<A> local_sp = shared_from_this();   //正确用法
        std::cout<<local_sp.use_count()<<std::endl;  //引用计数为2
    }   
};

int main()
{
    std::shared_ptr<A> sp(new A); 
    {   
        sp->func();
    }   

    return 0;
}

上述错误情况输出是:

这里写图片描述

正确情况输出是:
这里写图片描述

可见,使用this在类内部再造一个shared_ptr是多么可怕的事情,会造成多次释放。

在多线程环境环境中,我们可能在成员函数中需要将this的智能指针回调给别的类对象,就可使用shared_from_this()。这样,别的对象就能够知道本对象是否还活着,避免在死亡之后的时候调用,产生可怕的后果。
 

shared_ptr的技术与陷阱

  1. 意外延长对象生命周期。可能容器持有shared_ptr,或者boost::function持有,这都会不经意间延长对象的生命期。我们可能会通过让他们持有weak_ptr来避免生命期被延长。
  2. 函数参数,一个线程只要在最外层持有一个实体,那么安全不成问题。比如:

    void on_message(const string& msg){
        shared_ptr<foo> f(new foo(msg));  //在最外层有一个实体,然后将shared_ptr传给谁都可以,因为始终有一个引用计数为1存在。常引用方式传递效率更高。
        if(validate(f)){   //const reference
            save(f);  //const reference
        }
    }
    
  3. 虚析构不再是必须。因为shared_ptr有T和Y两个参数。使用base类的shared_ptr接收derived的shared_ptr,ref_count中维护的指针类型仍是T,即base*类型。而shared_ptr类中维护的才是Y。删除器还是按老样子删除。所以不要虚析构也成。

有一张图可以看一下:

这里写图片描述

暂时完毕。

掌握智能指针shared_ptr的精髓:底层原理与实际应用全解析 C++智能指针shared_ptr是一种可以自动管理内存的智能指针,它是C++11新增的特性之一。与传统指针不同,shared_ptr可以自动释放所管理的动态分配对象的内存,并避免了手动释放内存的繁琐操作,从而减少了内存泄漏和野指针的出现。shared_ptr是一个模板类,通过引用计数器实现多个智能指针共享对一个对象的所有权。每次复制一个shared_ptr对象时,该对象的引用计数器会增加1,当最后一个shared_ptr对象被销毁时,引用计数器减1,如果引用计数器变为0,则释放所管理的对象的内存。 阅读详情

相关推荐

ADS1220 STM32硬件SPI驱动程序

前一段时间温故了下标准库,使用的是F1系列芯片,测试程序。

随风飘零翼的博客 5770

C++智能指针详解:shared_ptr

C++没有内存回收机制,每次程序员new出来的对象需要手动delete,流程复杂时可能会漏掉delete,导致内存泄漏。于是C++引入智能指针,可用于动态资源管理,资源即对象的管理策略。 使用 raw pointer 管理动态内存时,经常会遇到这样的问题: 忘记delete内存,造成内存泄露。 出现异常时,不会执行delete,造成内存泄露。 下面的代码解释了,当一个操作发生异常时,会导致delete不会被执行: void func() { auto ptr = new Widget;

feikudai8460的博客 1万+

如何评价智能座舱的好坏之指标拆分?

智慧服务实际上是有一些比较实用的功能的,常用的有后视镜自动伸展、离车自动落锁、座椅记忆等,但这部分前装率较高,各家的区分度不高,所以一级指标中占比最低(10%)。不过呢,规则里的权重仅有5%,或许是考虑到比较成熟,没有区分度。全景环视明显拔得头筹,占比85%,这对于我这种没有倒车影像不敢倒车的人,举双手赞成,但还是要看其效果,比如,现在,测评规则的颁布实际上也让我们看到,智能座舱的技术方案正走向成熟,并将走进规范化运作。,占比 60%,这与我们对座舱最直接的认知符合,

shuiqingyan的博客 1084

C++智能指针shared_ptr用法

C++,shared_ptr对象管理指针,shared_ptr提供的接口, shared_ptr初始化,构造函数初始化,std::make_shared 初始化,reset初始化,shared_ptr管理指针的构造和析构,shared_ptr获取原始指针,shared_ptr的线程安全,shared_ptr应用之enable_shared_from_this.

weixin_40355471的博客 1万+

Swin Transformer注意力计算复杂度为何比全局注意力少那么多?

本文对比分析了Vision Transformer(ViT)和Swin Transformer的计算复杂度。ViT采用全局注意力机制,计算复杂度为4[hwc^2]+2[(hw)^2C],当图像尺寸较大时计算量显著增加。而Swin Transformer采用窗口注意力机制,将图像划分为M×M的窗口(如7×7),计算复杂度降为4[hwc^2]+2[(hw)(M*M)*C]。以224×224图像为例,Swin Transformer的计算量比ViT减少约900倍,大幅节省了计算资源。文章通过矩阵乘法运算详细推导了

人工智能研究所的博客 62

华为od 面试八股文_测试/测试开发_01_含答案

华为od面试八股文_测试/测试开发_01_含答案

MISAYAONE的博客 3816

智能指针——shared_ptr

1. shared_ptr的定义 shared_ptr是一种 智能指针( smart pointer)。shared_ptr的作用有如内 指针,但会记录有多少个tr1::shared_ptrs共同指向一个对象。这便是所谓的 引用计数(reference counting)。一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。这在非环形数据结构中防止资源泄露很有帮助。 2. shared_ptr的数据结构及示例 shared_ptr是引用计数型智能指针,几乎所有的实现都采用

weixin_45732589的博客 1万+

嵌入式系统开发笔记96:将STM32CubeIDE设置为中文

本文介绍将STM32CubeIDE设置为中文的方法。

snmplink的博客 8538

一、shared_ptr

动态内存与智能指针

LEandLA的博客 813

【华为OD机试真题】18、喊7的次数重排 | 机试真题+思路参考+代码解析(C++、Java、Py)

题目描述 >喊7是一个传统的聚会游戏,N个人围成一圈,按顺时针从1到N编号。   编号为1的人从1开始喊数,下一个人喊的数字为上一个人的数字加1,但是当将要喊出来的数字是7的倍数或者数字本身含有7的话,不能把这个数字直接喊出来,而是要喊”过”。   假定玩这个游戏的N个人都没有失误地在正确的时机喊了”过”,当喊到数字K时,可以统计每个人喊”过”的次数。   现给定一个长度为N的数组,存储了打乱顺序的每个人喊”过”的次数,请把它还原成正确的顺序,即数组的第i个元素存储编号i的人喊

KJ.JK 4167

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

cpp11新特性之智能指针(下):深入理解现代cpp中的智能指针shared_ptr、unique_ptr 以及 weak_ptr

上一篇文章同大家深入探讨了auto_ptr。今天给大家带来的是对于shared_ptrunique_ptr以及weak_ptr的深入理解,通过测试案例和源码剖析对这三种重要的智能指针的使用方法,注意事项以及适用范围进行了全面的总结,相信经过这些测试大家对于智能指针的理解会更上一层楼。

大龄工科菜鸡的cpp修行之路 1481

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

C++11智能指针之shared_ptr、weak_ptr

1. shared_ptr、weak_ptr概念 shared_ptr与weak_ptr智能指针均是C++ RAII的一种应用,可用于动态资源管理shared_ptr基于“引用计数”模型实现,多个shared_ptr可指向同一个动态对象,并维护了一个共享的引用计数器,记录了引用同一对象的shared_ptr实例。当最后一个指向动态对象的shared_ptr销毁时,会自动销毁其所指对象(通过d

4415

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

智能指针 之 unique_ptr shared_ptr weak_ptr

智能指针

weixin_45646601的博客 1107

智能指针 unique_ptr shared_ptr weak_ptr 的区别

c++11 智能指针 unique_ptr shared_ptr weak_ptr 区别

xkdlzy的专栏 1040

C++11学习笔记(6) —— smart pointer: shared_ptr , auto_ptr

1. 简介 C++程序员最痛苦的莫过于对内存的管理,由于没有像C# 和 java的内存回收机制,C++程序员在复杂的程序中容易造成内存的泄露。即使程序员十分小心,异常的产生也有可能会造成部分内存的泄露(由于异常导致delete语句没有机会执行)。为了避免该问题,出现了各种类型的智能指针。只能指针实质就是重载了-> 和 * 操作符的类,由类来实现对内存的管理,确保即使有异常产生,也可以通过智能指针

游戏乐园,happy 工作,happy生活 1207

share_ptr与weak_ptr的区别与联系

1.shared_ptr和weak_ptr 基础概念 shared_ptr与weak_ptr智能指针均是C++ RAII的一种应用,可用于动态资源管理 shared_ptr基于“引用计数”模型实现,多个shared_ptr可指向同一个动态对象,并维护了一个共享的引用计数器,记录了引用同一对象的shared_ptr实例的数量。当最后一个指向动态对象的shared_ptr销毁时,会自动销毁其所指对...

weixin_41066529的博客 9027

c++ 智能指针使用注意事项及解决方案

对智能指针的总结

Clarence的博客 815

C++ 智能指针

本文摘要: C++智能指针核心操作包括三种类型:std::unique_ptr(独占指针)、std::shared_ptr(共享指针)和std::weak_ptr。unique_ptr具有独占所有权特性,不可复制但可移动,推荐使用std::make_unique创建。shared_ptr采用引用计数机制,可共享所有权,通过use_count()查看引用数。weak_ptr用于解决shared_ptr循环引用问题。关键注意事项:unique_ptr创建后应将原始指针置空;shared_ptr不要与原始指针混用

m0_71288174的博客 946

深入理解 C++ 智能指针

智能指针和RAII(资源获取即初始化)是两个重要的概念,用于管理动态内存和其他资源。智能指针是一种自动管理内存的工具,通过引用计数或其他机制来确保资源的正确释放,从而避免内存泄漏和悬空指针的问题。RAII则是一种编程范式,利用对象的生命周期来管理资源的获取和释放,通过对象的构造函数获取资源,而在对象销毁时自动释放资源,从而确保资源的正确管理。本文介绍了C++中常见的智能指针类型,包括std::unique_ptr和std::shared_ptr,以及如何使用它们来避免内存泄漏和提高程序的安全性。

无敌岩雀的博客 1750

【C++】智能指针

实例对象的引用计数变为2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类TA、TB的实例对象不能被析构,最终造成内存泄露。也被析构,其对TA对象的管理解除,内存的引用计数减为1,当共享智能指针ap离开作用域之后,对TA对象的管理也解除了,内存的引用计数减为0,类TA的实例对象被析构。类型,这个赋值操作并不会增加引用计数,所以bp的引用计数仍然为1,在离开作用域之后bp的引用计数减为0,类TB的实例对象被析构。

IdealSanX的博客 2748

c++是否应避免使用普通指针,而使用智能指针(包括shared,unique,weak)?

转自:详解 C++ 11 中的智能指针 先说结论,是的,你应该尽量使用这些智能指针。 C/C++ 语言最为人所诟病的特性之一就是存在内存泄露问题,因此后来的大多数语言都提供了内置内存分配与释放功能,有的甚至干脆对语言的使用者屏蔽了内存指针这一概念。这里不置贬褒,手动分配内存与手动释放内存有利也有弊,自动分配内存和自动释放内存亦如此,这是两种不同的设计哲学。有人认为,内存如此重要的东西怎么能放心交给用户去管理呢?而另外一些人则认为,内存如此重要的东西怎么能放心交给系统去管理呢?在 C/C++ 语言中,内存泄露

weixin_49199646的博客 1544

C++ 智能指针使用注意事项

不使用auto_ptr unique_ptr可以指向动态数组。因为unique_ptr有unique_ptr<T[]>重载版本,销毁动态对象时调用delete[]: class A{}; unique_ptr<A[]> unique_ap(new A[1]{A()}); 使用unique_ptr,当需要共享对象所有权时,依然可以将其转化为shared_ptr,...

LU_ZHAO的博客 1982

C++智能指针详解

我们用new或者malloc申请一段空间之后,没有调用delete或者free,导致这部分申请的空间没有被释放,就像我们买了个房子,但是不记得房子的地址,我们也没办法入住使用这个空间,这个空间只能白白放着没法利用,这样就造成了资源浪费。如果使用移动构造的方式初始化智能指针对象,只是转让了内存的所有权,管理内存的对象不会增加,因此内存引用技术不会增加。std::unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针,

asdasd121312dasd的博客 664
上一篇: shared_ptr的线程安全性分析
下一篇: intrusive_ptr源码分析
FreeeLinux
博客等级 码龄11年 421粉丝 356原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值