shared_ptr使用场景、陷阱、性能分析,使用建议

2023-11-20

1.std::shared_ptr使用场景

 

#include <iostream>
#include <memory>


using namespace std;

shared_ptr<int> create0(int value) {
    return make_shared<int>(value); // 返回一个shared_ptr
}

//void myfunc(int value) {
shared_ptr<int> myfunc(int value) {
    shared_ptr<int> ptmp = create0(10);
    //return; // 离开作用域后,ptemp会被自动释放,它所指向的内存也会自动释放
    return ptmp; // 系统是根据ptmp这个局部变量来产生一个临时的shared_ptr对象往回返
}

int main() {
    //一:std::shared_ptr使用场景
    myfunc(12);
    // 如果这块不用shared_ptr变量来接收myfunc返回的结果,那么从myfunc返回的shared_ptr
    // 就会被销毁,所指向的对象也会被销毁
    auto p11 = myfunc(12);
    // 我们用了一个变量来接myfunc的返回值,那么myfunc返回的shared_ptr
    // 就不会被销毁,它所指向的对象也不会被销毁
    return 0;
}

二、std::shared_ptr使用陷阱分析:一旦用错,也是致命的

2.1慎用裸指针

避免使用匿名的临时shared_ptr<T>对象(Effective C++中写道)

#include <iostream>
#include <memory>

using namespace std;

void proc(shared_ptr<int> value) {
}

int main() {
    int*p = new int(100); //裸指针
    //proc(p); // 语法错, int*p 不能转换成shared_ptr<int>
    //error: could not convert ‘p’ from ‘int*’ to ‘std::shared_ptr<int>’
#if 0
    shared_ptr<int> p2(p);
    proc(p2);
#endif
    proc(shared_ptr<int>(p));  // 参数是个临时的shared_ptr,用一个裸指针显示的构造
    *p = 45; // 潜在的不可预料的问题;因为p指向的内存已经被释放了
    return 0;
}

2.2慎用get()返回的指针

返回只能指针指向的对象所对应的裸指针(有些函数接口可能只能使用裸指针)

get返回的指针不能delete,否则会异常

#include <iostream>                                                                                 
#include <memory>                                                                                   
                                                                                                    
using namespace std;                                                                                
                                                                                                    
int main() {                                                                                        
#if 0                                                                                               
    shared_ptr<int> myp(new int(100));                                                              
    int *p = myp.get();                                                                             
    delete p; // 不可以删除,会导致异常                                                             
#endif                                                                                              
    // 不能将其他智能指针绑到get返回的指针上                                                        
    shared_ptr<int> myp(new int(100));                                                              
    int *p = myp.get(); // 这个指针千万不能随意释放,否则myp就没办法正常管理该指针了                 
    {                                                                                               
         shared_ptr<int> myp2(p);// 错误,现在myp和myp2引用计数都为1,但一旦跳出这个程序块,p指针指向的内存被释放                 
        // shared_ptr<int> myp2;                                                                    
        // myp2 = shared_ptr<int>(myp); // 错误                                                     
        // shared_ptr<int> myp2(myp); // 正确                                                       
    }                                                                                               
    // 离开上边myp2的范围,导致myp指向的内存被释放了                                                 
    *myp = 65; //该内存已经被释放,这样复制会导致不可预料的后果                                     
    return 0;                                                                                       
} 

结论:永远不要用get得到的指针来初始化另一个智能指针或者给另改一个智能赋值

2.3不要把类对象指针(this)作为shared_ptr返回,改用enable_shared_from_this

#include <iostream>                                                                                 
#include <memory>                                                                                   
                                                                                                    
using namespace std;                                                                                
                                                                                                    
class CT: public enable_shared_from_this<CT> {                                                      
public:                                                                                             
    shared_ptr<CT> get_self() {                                                                     
        // return shared_ptr<CT>(this); // 用裸指针初始化多个shared_ptr的感觉                       
        return shared_from_this(); // 这个就是enable_shared_from_this类中的方法                     
    }                                                                                               
};                                                                                                  
                                                                                                    
int main() {                                                                                        
    shared_ptr<CT> pct1(new CT);                                                                    
    // shared_ptr<CT> pct2 = pct1; // 这两个强饮用;                                                 
    shared_ptr<CT> pct2 = pct1->get_self();                                                         
    cout << pct2.use_count() << endl; // 2                                                               
    // get_self函数如果使用裸指针,会出现问题                                                        
    // 用到c++标准库里边的类模板:enable_shared_from_this:                                           
    // 现在,在外面创建CT对象的智能指针以及通过CT对象返回的this智能指针都是安全的                    
    // 这个enable_shared_from_this中有一个弱指针weak_ptr,这个弱指针能够监视this                     
    // 在我们调用shared_from_this()这个方法时,这个方法内部实际上是调用weak_ptr的lock方法           
    // 大家都知道lock()会让shared_ptr指针计数+1,同时返回这个shared_ptr,这就是工作原理               
    return 0;                                                                                       
} 

2.4避免循环引用:能够导致内存泄露

妖异的代码

#include <iostream>
#include <memory>

using namespace std;

class CB;
class CA {
public:
    shared_ptr<CB> m_pbs;
    ~CA() {
        int test;
        test =1;
        cout << "~CA()" << endl;
    }
};

class CB {
public:
    //shared_ptr<CA> m_pas;
    weak_ptr<CA> m_pas; // 把这里变成弱引用
    ~CB() {
        int test;
        test =1;
        cout << "~CB()" << endl;
    }
};

int main() {
#if 0
    shared_ptr<CA> pca(new CA);
    shared_ptr<CB> pcb(new CB);
    pca->m_pbs = pcb;  // 等价于指向CB对象的有两个强引用
    pcb->m_pas = pca; // 妖异,等价于指向CA对象的有两个强引用
#endif

    shared_ptr<CA> pca(new CA);
    shared_ptr<CB> pcb(new CB);
    pca->m_pbs = pcb;  // 等价于指向CB对象的有两个强引用
    pcb->m_pas = pca; // 因为m_pas是弱引用,所以这里指向CA的对象只有一个强引用
    // 离开作用域之后,pca引用技术从1就变成0会释放CA对象(CA的析构函数被执行)
    // CA的析构函数被执行了(表示对象即将被释放),
    // 导致CA内的m_pbs引用技术会减1,也就是指向CB对象的引用技术-1
    // 超出pcb作用域时指向CB的计数也会-1,
    // 最终,会有一个时刻,指向CB对象的强引用计数从1较少到0,导致CB对象被释放 
    return 0;
}

三、性能说明

3.1尺寸问题

#include <iostream>
#include <memory>

using namespace std;


int main() {
    char *p;
    int lenp = sizeof(p); // 4字节
    cout << lenp << endl;
    shared_ptr<string> p1;
    int ilensp = sizeof(p1);
    cout << ilensp << endl; // 8字节,包含两个裸指针
    return 0;
}

shared_ptr的尺寸是裸指针的2倍;weak_ptr尺寸裸指针的2倍

a)第一个裸指针指向的是这个智能指针所指向的对象

b)第二个裸指针指向一个很大的数据结构(控制块),这个控制块里边有啥:

    b.1)所指对象的强引用计数:shared_ptr

    b.2)所指对象的弱引用计数:weak_ptr

    b.3)其他数据,比如删除器指针,内存分配器

这个控制块是由第一个指向某个对象的shared_ptr创建的

控制块创建时机:

a)make_shared:分配并初始化一个对象,返回指向对象的shared_ptr,所以,这个make_shared它总是能够创建一个控制块
shared_ptr<int> p2 = make_shared<int>(100);
b)用裸指针来创建一个shared_ptr对象时
int*p = new int();
shared_ptr<int> p1(pi);
shared_ptr<int> p2(pi); // 不允许,否则会产生多个控制块,也就是多个引用计数(每个都是1);析构时析构多次,导致异常

3.2.移动语义

shared_ptr<int> p1(new int(100));
shared_ptr<int> p2(std::move(p1));  // 移动语义,移动构造一个新的智能指针p2
                                 // p1就不再指向该对象(变成空),引用计数依旧是1
shared_ptr<int> p3;
p3 = std::move(p2); // 移动赋值,p2指向空, p3指向该对象,整个对象的引用计数仍旧为1

移动肯定比复制快;复制你要增加引用技术,移动不需要,

移动构造函数快过复制构造函数,移动赋值运算符快过拷贝赋值运算符

4.补充说明和使用建议

a)掌握了绝大部分shared_ptr用法;小部分没讲解,靠大家摸索

分配器,解决了内存分配问题

shared_ptr<int> p((new int), mydeleted(), myallocator<int>())

b)谨慎使用

new shared_ptr<int>, memcpy() 奇怪用法,大家不要轻易尝试

c)优先使用make_shared(),不能让自己定义自己的删除器

shared_ptr<string> ps1(new string("I Love China!")); // 分配两次
auto ps2 = make_shared_<string>("I love Chind!"); // 分配1次内存

5.多线程下shared_ptr

(1)一个 shared_ptr 对象实体可被多个线程同时读取;

(2)两个 shared_ptr 对象实体可以被两个线程同时写入,“析构”算写操作;

(3) 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。

   为什么多线程读写 shared_ptr 要加锁?_陈硕的博客-CSDN博客_多线程读写

(4)多线程的情况下,shared_ptr的use_count成员技数是不准确的。业务场景:如果使用use_count是否等于1,判断是否有其他线程在使用。是不正确的。

常用使用包括

  • 与 ​0​ 比较。若 use_count 返回零,则智能指针为且不管理对象(无论被存储指针是否为空)。多线程环境下,这不隐含被管理对象的析构函数已完成。
  • 与 1 比较。若 use_count 返回 1 ,则无其他拥有者。(被弃用成员函数 unique() 为此使用情况提供。)多线程环境中,这不隐含对象可以安全修改,因为先前拥有者对被管理对象的访问可能未完成,而因为新的共享拥有者可以同时引入,例如用 std::weak_ptr::lock 。

std::memory_order - cppreference.com

std::shared_ptr<T>::use_count - cppreference.com

深入RPC - Atomic instructions - 《BRPC v0.9.7 开发手册》 - 书栈网 · BookStack

多线程安全引用计数读写:https://github.com/apache/incubator-brpc/blob/master/src/butil/memory/scoped_ptr.h

 

 

 

 

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

shared_ptr使用场景、陷阱、性能分析,使用建议 的相关文章

随机推荐

  • ET篇:斗地主的流程(资源工作流)

    有了master的学习经验 斗地主的学习将不会太多精细化 更多细节大家可以自行查看 本系列文章旨在帮助大家理解整个开发流程 资源划分策略 先来到Asset下的Bundles文件夹 这里是游戏内用到的所有的资源 都被打成ab包 正式发布时将会
  • 「 每日一练,快乐水题 」14. 最长公共前缀

    文章目录 力扣原题 题目简述 解题思路 C 代码 结果展示 力扣原题 14 最长公共前缀 题目简述 编写一个函数来查找字符串数组中的最长公共前缀 如果不存在公共前缀 返回空字符串 示例 1 输入 strs flower flow fligh
  • 一键设置L2TP脚本-Ubuntu14.04LTS

    亲测在Vultr和UltraVPS的Ubuntu 14 04 LTS成功搭建L2TP的VPN 本方法使用Linux自带的账户认证作为L2TP的认证 用户名默认为vpn user 密码在脚本执行过程中 由执行者手动设定密码 PSK为psk 开
  • linux中安装nginx

    2 安装nginx 2 1 安装nginx前 需要安装的依赖 可能是由于nginx版本旧原因 可能最新或较新版本不需安装这些依赖 如下四个依赖需要安装到linux中 2 1 1 安装 pcre 依赖 使用wget命令 步骤一 执行下面这个命
  • Debug断点无法执行

    1 可能是没有启动debug模式 导致无法进入 2 可能是idea中有缓存 导致直接出结果 这种选择重新启动项目即可
  • 浅谈core.js

    浅谈core js core js 前言 core js是什么 官方描述 总结 core js 前言 core js的作者 Denis Pushkarev 很有名 平时爱好就是飙摩托车 在一次事故中 酒驾 他以60km h的速度驾驶 结果撞
  • 2.5 Git 基础 - 远程仓库的使用

    2 5 Git 基础 远程仓库的使用 版本说明 版本 作者 日期 备注 0 1 loon 2019 3 21 初稿 目录 文章目录 2 5 Git 基础 远程仓库的使用 版本说明 目录 远程仓库的使用 1 查看远程仓库 2 添加远程仓库 3
  • 【大屏可视化模板】vue-dataV-echarts-elementul大屏数据可视化方案,屏幕适配方案等比例缩放

    效果图 从上到下 依次是F11效果 和正常网页效果 以及小屏效果 都是同比例缩放的 布局不会混乱 聊一下 为了让大家直观的看到所有的代码 所以结构方法等就不分各个组件引入了 会很麻烦要找哪是哪 我直接把所有的图都写在了一个vue组件内 并配
  • 电脑有网但打不开网页怎么办?

    明明刚交了宽带年费 本地连接显示一切正常 但是打开网页总有问题 换浏览器重启无效 我该怎么办 放心吧 下面 微点阅读小编整理了一些网络链接正常却上不了网的解决方法 对于经常上网的朋友来说 除了手机购物 在Pc端玩网页游戏是很多小伙伴的首选
  • idea 启动时怎么选择工作空间

    idea 启动时怎么选择工作空间 按快捷键 ctrl alt s打开设置 点击System Settings选项后 把右边版面中Reopen last projecton startup前面的勾去掉 保存 下次再打开的时候就可以选择你要的空
  • 关于Redis的事件回调解析以及docker中的配置

    基本概念 Redis的过期回调可以实现我们的redsi的key在过期的时候回调一些接口从而来实现项目中需要的一些功能 比如我们想在订单超时的时候进行关闭 可以用这个来进行一个简单的实现 当然实际的项目中能否这样使用我们暂且不做讨论 这里只是
  • word添加字体库

    1001 Fonts Free Fonts Baby 51044 free fonts in 28637 families Free licenses for commercial use Direct font downloads Mac
  • 一个数组有 N 个元素,求连续子数组的最大和(动态规划问题)

    该题题目如上 例如 1 2 1 连续的最大子数组为 2 1 和为3 题目要求我们输入第一个数为数组元素的个数 然后后面为我们需要输入的元素 遇到这一个题 我们首先可以这样考虑 设置一个sum和result sum是用来每次加新的元素 res
  • Angular-1.5.8文档翻译之$compile

    对照地址 https code angularjs org 1 5 8 docs api ng service compile compile是将一个DOM字符串或者一个DOM进行编译并返回一个模板链接函数 这个链接函数可以用于将scope
  • Google 在 ChatGPT 时代的生死之战:居然把 DeepMind 和 Google Brain 合并了

    今天一大早 6 点起来 居然看到 Google 将 DeepMind 和 Google Brain 合并为 Google DeepMind 了 Google and Alphabet CEO Sundar Pichai DeepMind 创
  • 计算机基础之组成原理

    计算机组成原理 一 计算机的基本硬件组成 CPU 内存 主板 I O 设备 显卡 二 计算机如何执行指令 计算机指令 CPU如何执行指令 CPU 内部处理过程 CPU 的寄存器 程序计数器 条件分支和循环机制 CPU 指令执行过程 内存 内
  • 编译micropython中的mpycross

    root charles VirtualBox media sf Linux micropython master make C mpy cross make Entering directory media sf Linux microp
  • MySQL——基础50题

    MySql数据库50题 准备工作 参考答案 use PraticeSql create table SC SId varchar 10 CId varchar 10 score decimal 18 1 insert into SC val
  • Freertos 在contexM0芯片上的移植

    1 freertos源码 官网下载地址 https www freertos org 下载LTS长期支持版本 2 在工程目录下新建FreeRTOS文件夹 将FreeRTOS 的源码添加到这个文件夹中 portable 文件夹中只需要复制 k
  • shared_ptr使用场景、陷阱、性能分析,使用建议

    1 std shared ptr使用场景 include