C++11 新特性 ⑥ | 智能指针 unique_ptr、shared_ptr 和 weak_ptr

news2024/11/24 11:30:11

目录

1、引言

2、unique_ptr

3、shared_ptr

4、weak_ptr

5、shared_ptr循环引用问题(面试题)


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html       C++11新特性很重要,作为C++开发人员很有必要去了解去学习,不仅笔试面试时会涉及到,开源代码中会大规模的使用。以很多视频会议及直播软件都在使用的开源WebRTC项目为例,WebRTC代码中大篇幅地使用了C++11及以上的新特性,要读懂其源码,必须要了解这些C++的新特性。所以,接下来一段时间我将结合工作实践,给大家详细讲解一下C++11的新特性,以供借鉴或参考。

1、引言

       C++程序中使用的大部分内存都是动态申请来的堆内存,动态申请来的内存很容易出问题,要在正确的时间去释放内存没那么容易。有时我们如果忘记释放内存,就会产生内存泄漏(持续的内存泄漏会导致Out of memory内存耗尽);有时在尚有指针在引用内存的情况下我们就将内存释放了,就会产生引用非法内存的指针,就是我们常讲的野指针(或叫做悬空指针)。

       为了更容易、更安全地使用动态申请的内存,C++中引入了智能指针的概念。其实早在1998年发布的C++98标准中就引入了智能指针auto_ptr,不过auto_ptr有一些缺陷,现在已经被废弃了。C++11中引入了unique_ptr、shared_ptr和weak_ptr三个智能指针,位于C++标准STL库中,在 <memory> 头文件中的 std 命名空间中定义的。

       智能指针的大体机制和原理为:

C++智能指针用来存储指向动态分配(堆)对象指针的类,用于C++类对象的生存期的控制,确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每引用C++对象1次,内部引用计数就加1;智能指针每析构1次,内部的引用计数就减1,当引用计数减为0时,就会删除指向的C++类对象(释放类对象的堆内存)。

       C++11标准中主要使用unique_ptr和shared_ptr两智能指针,weak_ptr则主要用来辅助shared_ptr,避免出现循环引用问题的。在使用unique_ptr和shared_ptr两个智能指针类时,可以使用指针运算符(-> 和 *)访问指向的对象,因为智能指针类重载了->和*运算符,以返回指向的对象(指针),相关代码如下:

_NODISCARD add_lvalue_reference_t<_Ty> operator*() const
    {    // return reference to object
        return (*get());
    }
 
_NODISCARD pointer operator->() const noexcept
    {    // return pointer to class object
        return (this->_Myptr());
    }

其中,get接口的实现如下:

_NODISCARD pointer get() const noexcept
    {    // return pointer to object
        return (this->_Myptr());
    }

2、unique_ptr

       unique_ptr是独占式智能指针,它拥有对其所指向对象的唯一所有权。unique_ptr 指针被销毁时,它所指向的对象也会被销毁。由于其具有独占性,所以unique_ptr不能被拷贝,只能被转移所有权。将对象的所有权转移到新的unique_ptr对象中,原先的unique_ptr对象不再指向原来的对象。

       unique_ptr持有对对象的独有权,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。

       使用unique_ptr的实例如下:

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    unique_ptr<int> up1(new int(11));   // 无法复制的unique_ptr
    //unique_ptr<int> up2 = up1;        // err, 不能通过编译
    cout << *up1 << endl;   // 11

    unique_ptr<int> up3 = move(up1);    // 现在p3是数据的唯一的unique_ptr

    cout << *up3 << endl;   // 11
    //cout << *up1 << endl;   // err, 运行时错误
    up3.reset();            // 显式释放内存
    up1.reset();            // 不会导致运行时错误
    //cout << *up3 << endl;   // err, 运行时错误

    unique_ptr<int> up4(new int(22));   // 无法复制的unique_ptr
    up4.reset(new int(44)); //"绑定"动态对象
    cout << *up4 << endl;

    up4 = nullptr;//显式销毁所指对象,同时智能指针变为空指针。与up4.reset()等价

    unique_ptr<int> up5(new int(55));
    int *p = up5.release(); //只是释放控制权,不会释放内存
    cout << *p << endl;
    //cout << *up5 << endl; // err, 运行时错误
    delete p; //释放堆区资源

    return 0;
}

3、shared_ptr

       shared_ptr是共享式智能指针,一个对象可以被多个shared_ptr共享。每个shared_ptr内部都维护一个引用计数,当引用计数为 0 时,所指向的对象会被销毁。std::shared_ptr 可以被拷贝和移动。

       shared_ptr允许多个该智能指针共享第“拥有”同一堆分配对象的内存,这通过引用计数(reference counting)实现,会记录有多少个shared_ptr共同指向一个对象,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。

       使用shared_ptr的实例如下:

int main()
{
    shared_ptr<int> sp1(new int(22));
    shared_ptr<int> sp2 = sp1;

    cout << "count: " << sp2.use_count() << endl; //打印引用计数

    cout << *sp1 << endl;   // 22
    cout << *sp2 << endl;   // 22

    sp1.reset();    //显式让引用计数减1
    cout << "count: " << sp2.use_count() << endl; //打印引用计数

    cout << *sp2 << endl;   // 22

    return 0;
}

4、weak_ptr

        weak_ptr是为了配合shared_ptr而引入的一种智能指针,它不具有普通指针的行为,没有重载*和->两个操作符,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。

        weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源 (也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr 获得一个可用的shared_ptr对象,从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。

       使用weak_ptr的实例如下:

void check(weak_ptr<int> &wp)
{
    shared_ptr<int> sp = wp.lock(); // 转换为shared_ptr<int>
    if (sp != nullptr)
    {
        cout << "still " << *sp << endl;
    }
    else
    {
        cout << "pointer is invalid" << endl;
    }
}

int main()
{
    shared_ptr<int> sp1(new int(22));
    shared_ptr<int> sp2 = sp1;
    weak_ptr<int> wp = sp1; // 指向shared_ptr<int>所指对象

    cout << "count: " << wp.use_count() << endl; //打印计数器
    cout << *sp1 << endl;   // 22
    cout << *sp2 << endl;   // 22

    check(wp);              // still 22

    sp1.reset();
    cout << "count: " << wp.use_count() << endl;

    cout << *sp2 << endl;   // 22
    check(wp);              // still 22

    sp2.reset();
    cout << "count: " << wp.use_count() << endl;
    check(wp);              // pointer is invalid

    return 0;
}

5、shared_ptr循环引用问题(面试题)

       使用shared_ptr可能会出现循环引用问题,场景是两个类中都包含了指向对方的shared_ptr对象,这样会导致new出来的两个类没有走析构,引发内存泄漏问题。

       循环引用问题的示意图如下:

相关代码如下:

#include <iostream>
#include<memory>
 
using namespace std;
 
class B;
class A{
public:
    shared_ptr<B> bptr;
    ~A(){cout<<"~A()"<<endl;}
}
 
class B
{
public:
    shared_ptr<A> aptr;
    ~B( ){cout<<"~B()"<<endl;}
}

int main() 
{
    shared_ptr<A> pa(new A()); // 引用加1
    shared_ptr<B> pb(new B()); // 引用加1
    pa->bptr = pb; // 引用加1
    pa->aptr = pa; // 引用加1
    return 0;
}

       执行到上述return 0这句代码时,指向A和B两个对象的引用计数都是2。当退出main函数时,先析构shared_ptr<B> pb对象,B对象的引用计数减1,B对象的引用计数还为1,所以不会delete B对象,不会进入B对象析构函数,所以B类中的shared_ptr<A> aptr成员不会析构,所以此时A对象的引用计数还是2。当析构shared_ptr<A> pa时,A的引用计数减1,A对象的引用计数变为1,所以不会析构A对象。所以上述代码会导致A和B两个new出的对象都没释放,导致内存泄漏。

       为了解决上述问题,引入了weak_ptr,可以将类中包含的shared_ptr成员换成weak_ptr,如下:

相关代码如下:

#include <iostream>
#include<nemory>
 
using namespace std;
 
class B;
class A{
public:
    weak_ptr<B> bptr;  // 使用weak_ptr替代shared_ptr
    ~A(){cout<<"~A()"<<endl;}
}
 
class B
{
public:
    weak_ptr<A> aptr; // 使用weak_ptr替代shared_ptr
    ~B( ){cout<<"~B()"<<endl;}
}
 
int main() 
{
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());
    pa->bptr = pb;
    pa->aptr = pa;
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/995174.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java之List集合的解析及泛型的概述

4.List集合的实现类 4.1List集合子类的特点【记忆】 ArrayList集合 底层是数组结构实现&#xff0c;查询快、增删慢 LinkedList集合 底层是链表结构实现&#xff0c;查询慢、增删快 4.2LinkedList集合的特有功能【应用】 特有方法 方法名说明public void addFirst(E e)在该…

小白备战大厂算法笔试(五)——树

文章目录 二叉树常用术语初始化插入与删除常见类型满二叉树完全二叉树完满二叉树平衡二叉树 二叉树退化二叉树遍历层序遍历前序、中序、后序遍历 数组表示二叉树表示完美二叉树表示任意二叉树 二叉搜索树查找节点插入节点删除节点遍历有序搜索效率常见应用 二叉树 二叉树是一种…

Dynamic CRM开发 - 实体窗体(一)

CRM创建一个实体以及实体字段后,在实体窗体里拖动字段和简单配置,就可以实现一个新增和编辑实体数据的表单,这种无代码的操作形式对非程序员非常的友好。 实体有四种类型的窗体:主窗体、快速视图窗体、快速创建窗体、卡窗体。 窗体区别如下: 窗体操作说明: 1、启用安全…

Windows定时任务实现开机自启动

Windows定时任务实现S11开机自启动 Windows键加 r 键打开运行窗口&#xff0c;然后输入control打开控制面板。

Linux内核分析与应用2-内存寻址

本系列是对 陈莉君 老师 Linux 内核分析与应用[1] 的学习与记录。讲的非常之好&#xff0c;推荐观看 留此记录&#xff0c;蜻蜓点水,可作抛砖引玉 2.1 内存寻址 数据连续存储和选择读取思想,是目前我们使用的几乎所有机器运行背后的灵魂 计算机体系结构中的核心问题之一,就是如…

python28种极坐标绘图函数总结

文章目录 基础图误差线等高线polar场图polar统计图非结构坐标图 &#x1f4ca;python35种绘图函数总结&#xff0c;3D、统计、流场&#xff0c;实用性拉满 matplotlib中的画图函数&#xff0c;大部分情况下只要声明坐标映射是polar&#xff0c;就都可以画出对应的极坐标图。但…

深入浅出AXI协议(6)——传输属性

一、前言 在之前的文章中&#xff0c;我们介绍的主要内容是AXI协议的数据读写结构和读写响应结构&#xff0c;主要讲述了当遇到各种特殊情况时,AXI如何完成数据的读写操作&#xff0c;最后介绍了读写响应的4种类型。 在本文中&#xff0c;我们将介绍AXI协议的传输属性。 二、传…

centos7的yum修改为阿里源

yum修改为阿里源 1.安装wget yum install -y wget2.备份与下载源 cd /etc/yum.repos.d mv -f CentOS-Base.repo CentOS-Base.repo.backup wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo mv epel.repo epel.repo.backup # 有些系…

C++引用与移动语义

目录 一.引用分类 1.名词解释 1).左右值 二.引用&#xff08;左值引用&#xff09; 1.左值引用&#xff08;Lvalue Reference&#xff09;&#xff1a; 2.本质 3.形式 4.注意 5.示例 1&#xff09;引用做左值 2&#xff09;引用做函数返回值 三.右值引用 1.右值引…

代码随想录算法训练营day|139.单词拆分|多重背包基础力理论| 背包总结

139.单词拆分 力扣题目链接 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict&#xff0c;判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明&#xff1a; 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1&#xff1a…

centos 端口被占用的快速排查方式

问题笔记 centos 端口被占用的快速排查方式 centos 端口被占用的快速排查方式 这里说一个我刚刚遇到的问题&#xff0c;解决步骤用来记录&#xff0c;方便以后自己查询。 nginx配置完index.html测试文件&#xff0c;发现一直显示的404页面。 我跑到服务器上想重启一下nginx …

如何从Git上拉取项目

1.Git的概念 Git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速的处理从很小到非常大的项目版本管理。它实现多人协作的机制是利用clone命令将项目从远程库拉取到本地库&#xff0c;做完相应的操作后再利用push命令从本地库将项目提交至远程库。 2.Git的工作流程 …

Meshmixer在数字牙科的实践

数字牙科&#xff08;Digital Dentistry&#xff09;在口腔健康领域获得越来越多的空间&#xff0c;如何使用 Meshmixer 软件在数字牙科中创建 3D 模型对于该领域的专业人士来说是一项宝贵的技能。 在本文中&#xff0c;拟将学习如何掌握这个强大的工具并创建令人惊叹的 3D 模型…

本地MQTT服务器搭建(EMQX)

一、下载EMQX 下载地址&#xff1a;EMQ (emqx.com) 打开官网后&#xff0c;选择右边的免费试用按钮 然后单击EMQX Enterprise标签&#xff0c;然后选择下面的EMQX开源版&#xff0c;选择开源版的系统平台为Windows&#xff0c;单击免费下载。 在新页面下单击立即下载 二、安装…

第1章_瑞萨MCU零基础入门系列教程之单片机程序的设计模式

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

华为云云耀云服务器L实例评测|安装Java8环境 配置环境变量 spring项目部署 【!】存在问题未解决

目录 引出安装JDK8环境查看是否有默认jar上传Linux版本的jar包解压压缩包配置环境变量 上传jar包以及运行问题上传Jar包运行控制台开放端口访问失败—见问题记录关闭Jar的方式1.进程kill -92.ctrl c退出 问题记录&#xff1a;【!】未解决各种方式查看端口情况联系工程师最后排查…

自学Python05-学会Python中的函数定义

亲爱的同学们&#xff0c;今天我们将开始学习 Python 中的函数。函数就像一个魔法盒子&#xff0c;可以让我们在程序中执行一段代码&#xff0c;并且可以反复使用。这样&#xff0c;我们的程序就可以变得更加简洁和易于理解。现在&#xff0c;让我们一起来学习如何使用函数吧&a…

无涯教程-JavaScript - OCT2HEX函数

描述 OCT2HEX函数将八进制数转换为十六进制。 语法 OCT2HEX (number, [places])争论 Argument描述Required/OptionalNumber 您要转换的八进制数。 数字不得超过10个八进制字符(30位)。数字的最高有效位是符号位。其余的29位是幅度位。 负数使用二进制补码表示。 RequiredPl…

报考浙江工业大学MBA项目如何选择合适的辅导班?

浙江工业大学MBA项目每年有数百人报考&#xff0c;在浙江省内除了浙大以外算是人数比较多的一个项目。2023级的招生中第一志愿也通过复试刷掉了百来人&#xff0c;在省内其实作为第一志愿报考的风险在逐渐增大&#xff0c;考生们如果坚持报考&#xff0c;则在针对联考初试的备考…

B站:AB Test 知识全解

AB Test的实质&#xff1a;假设检验&#xff0c;主要有以下几个步骤&#xff1a; 1、在实验开始前&#xff0c;找产品、项目经理等确认&#xff1a;实验需要验证的改动点&#xff08;一次只能看一个&#xff01;&#xff01;&#xff01;&#xff09; 2、数据分析师设计需要去观…