C++11 智能指针详解

news2024/11/19 9:29:40
        C++ 程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己 管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11 中引入了智能指针的 概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常 时内存泄露等问题等,使用智能指针能更好的管理堆内存。
        C++ 里面的四个智能指针 : auto_ptr,unique_ptr,shared_ptr, weak_ptr  其中后三个是 C++11 支持,并且第一个已经被C++11 弃用。

目录

一. shared_ptr(共享智能指针)

1.shared_ptr的基本用法

1)初始化

2)获取原始指针

2.shared_ptr使用问题

1)不能用一个原始指针初始化多个shared_ptr,更不能初始化非堆的内存。

2)不能在函数实参中创建智能指针

3)不要将this指针作为shared_ptr返回回来。

4)循环引用

二 . unique_ptr(独占智能指针)

        1. 不允许赋值

        2.通过std::move 移动

        3.使用make_unique效率更高(C++14引入)

        4. unique_ptr 指定删除器

三 . weak_ptr(弱引用智能指针)     

 1.基本用法

  1).weak_ptr使用

  2). 通过expired()方法判断所观察资源是否已经释放

  3). 通过lock方法获取监视的shared_ptr

  2. weak_ptr返回this指针

  3. 解决循环引用

四:智能指针使用安全问题


一. shared_ptr(共享智能指针)

        
        shared_ptr共享被管理对象,同一时刻可以有多个 shared_ptr 拥有对象的所有权,当最后一个
shared_ptr 对象销毁时,被管理对象自动销毁。
        shared_ptr使用引用计数(use_count),每一个shared_ptr 的拷贝都指向相同的内存。再最后一个 shared_ptr 析 构的时候,内存才会被释放。
        

   shared_ptr实现包含了两部分:
  •   一个指向堆上创建的对象的裸指针,raw_ptr
  • 一个指向内部隐藏的、共享的管理对象。 share_count_object

1.shared_ptr的基本用法

1)初始化

通过构造函数、 std::shared_ptr 辅助函数和 reset 方法来初始化 shared_ptr
std::shared_ptr<int> p1(new int(1)); 
std::shared_ptr<int> p2 = p1; 
std::shared_ptr<int> p3; 
p3.reset(new int(1));  //构建对象
p3.reset();            //析构对象

注意:

  • 如果使用考虑效率,可以使用make_shared

  这是因为:make_shared 通常具有更好的性能,因为它可以在一次堆分配中同时分配对象和控制块,而不是两次分配(一个用于对象,一个用于控制块),而普通的shared是进行两次分配,先分配一个内存块用于存储对象,然后再分配另一个内存块用于存储控制块,这就是两次分配。

 auto sp1 = make_shared<int>(100)  等价于 shared_ptr<int> sp1(new int(100));
  • 不能将一个原始指针直接赋值给一个智能指针,需要通过构造函数和辅助方法(隐式转换或reset)来初始化,下述行为时错误的
    std::shared_ptr<int> p = new int(1);
  •  上述示例中,p1和p2不是同一个地址,p1.get()和p2.get() 才是同一个地址,共同指向raw_ptr

2)获取原始指针

当需要原始指针的时候。可以通过调用get返回luo'zhi
shared_ptr<TEST> p3(p1);  //引用计数+1
p3.reset(new TEST); //reset有参数表示分配资源, 无参表示释放资源
TEST *p = p3.get();

注意:

  • 不要delete p.get()的返回值 ,会导致对一块内存delete两次的错误
  • 不要保存 p.get() 的返回值 ,无论是保存为裸指针还是 shared_ptr 都是错误的
  • 保存为裸指针不知什么时候就会变成空悬指针,保存为 shared_ptr 则产生了独立指针
    3)指定删除器
    当shared_ptr管理非new的对象或者没有析构函数的对象的时候,应该传递合适的删除器
    3-1示例表示自动关系析构函数
    3-1) 函数做删除器
    void release_source(TEST *p)
    {
        cout<<"release_source"<<endl;
        delete p;
    }
    
    int main()
    {
        shared_ptr<TEST> p1(new TEST,release_source);
        return 0;
    }
      

      

        3-2) Lambda表达式做删除器    
shared_ptr<TEST> p2(new TEST,[](TEST *p){
        cout<<"release_source"<<endl;
        delete p;});

  注意:

  •  当智能指针管理数组的时候需要指定删除器或使用模板类default_delete(因为shared_ptr默认删除器不支持数组对) 

  原因:智能指针默认的删除器(deleter)是针对单个对象的,而不是数组对象。这是因为C++中数组的内存布局和单个对象是不同的。数组通常需要在内存中连续存储多个元素,而单个对象只占用一个内存块。C++11引入了std::shared_ptr的一个专门版本:std::shared_ptr<T[]>,它支持管理数组对象。当你使用std::shared_ptr<T[]>时,你需要提供一个删除器,该删除器知道如何使用delete[]来释放内存,从而正确处理数组。

std::shared_ptr<int> p3(new int[10], [](int *p) { delete [] p;});
std::shared_ptr<int>ptr(new int[10],std::default_delete<int[]>());

2.shared_ptr使用问题

1)不能用一个原始指针初始化多个shared_ptr,更不能初始化非堆的内存。

int *p =  new int;
shared_ptr<int>ptr1(p);
shared_ptr<int>ptr2(p); //逻辑错误

2)不能在函数实参中创建智能指针

function(shared_ptr<int>(new int), g()); //有缺陷

C++函数参数对于不同的编译器执行过程可能时不同的,可能从左到右或从右到左。假设想new int,然后调用g().此时g发生了异常 ,shared_ptr还没创建完成(用make_shared会快一些),就会造成内存泄漏

正确做法:

shared_ptr<int> p(new int);
function(p, g());

3)不要将this指针作为shared_ptr返回回来。

        this指针本质也是一个裸指针,可能会造成重复析构

class TEST
{
public:
    TEST()
    {
        cout<<"new TEST"<<endl;
    }
    ~TEST()
    {
        cout<<"delete TEST"<<endl;
    }
    shared_ptr<TEST>get_ptr()
    {
        return shared_ptr<TEST>(this);
    }
};

int main()
{

    shared_ptr<TEST>p1(new TEST);
    shared_ptr<TEST>p2 = p1->get_ptr();
    return 0;

}

 在这个例子中,由于用同一个指针(this)构造了两个智能指针sp1sp2而他们之间是没有任何关系 的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误

正确做法: 让目标类通过 std::enable_shared_from_this 类,然后使用基类的
成员函数 shared_from_this() 来返回 this shared_ptr
class TEST:public enable_shared_from_this<TEST>
{
public:
    TEST()
    {
        cout<<"new TEST"<<endl;
    }
    ~TEST()
    {
        cout<<"delete TEST"<<endl;
    }
    shared_ptr<TEST>get_ptr()
    {
        return shared_from_this();
        // return shared_ptr<TEST>(this);
    }
};

  4)循环引用

        智能指针shared_ptr的出现可以使得同一资源被多个指针共享,并且保证共享资源只被释放一次,其内部使用计数器原理。但是两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。

看案例:

#include <string.h>
#include <unistd.h>
#include<iostream>
#include <memory>

using namespace std;

class B;

class A 
{
public:
    shared_ptr<B> pb_;  
    ~A()
    {
        cout<<"A delete"<<endl;
    }   
};

class B
{

public:
    shared_ptr<A> pa_;
    ~B()
    {
        cout<<"B delete"<<endl;
    }

};


int main()
{
    shared_ptr<B> pb(new B());
    shared_ptr<A> pa(new A());

    pb->pa_ = pa;
    pa->pb_ = pb;
    cout<<pb.use_count()<<endl;
    cout<<pa.use_count()<<endl;
    
    return 0;
}

 解决办法:

        下面会讲

        weak_ptr对象指向shared_ptr对象时(反之亦然),不会增加shared_ptr中的引用计数。

        当然使用weak_ptr的时候需要注意访问对象的方法。

shared_ptr<B> pb_ -> weak_ptr<B> pb_;

二 . unique_ptr(独占智能指针)

        独占智能指针不允许其他智能指针共享,也不允许通过将一个 unique_ptr赋值到unique_ptr中。但是unique_ptr可以通过move操作移动unique_ptr(原指针失效) 当然也可以通过返回进行返回。以下举例:

        1. 不允许赋值

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

class TEST
{
public:
    TEST()
    {
        cout<<"new TEST"<<endl;
    }
    ~TEST()
    {
        cout<<"delete TEST"<<endl;
    }
};

int main()
{
    unique_ptr<TEST> p1(new TEST);
    unique_ptr<TEST> p2(p1); //错误

    return 0;
}

        2.通过std::move 移动

unique_ptr<TEST> p1(new TEST);
unique_ptr<TEST> p2(std::move(p1)); //正确

        3.使用make_unique效率更高(C++14引入)

auto p3(make_unique<TEST>());

        4. unique_ptr 指定删除器

        对于shared_ptr,指定删除器        

shared_ptr<int>p1(new int, [](int *p){delete p;});

       对于unique_ptr,指定删除器需要确定删除器的类型

unique_ptr<int, void(*)(int *)> p6(new int, [](int *p){delete p;});

        比如指针是指向一个数组,对于共享指针,默认删除器是不能删除数组的,需要注意。

  

三 . weak_ptr(弱引用智能指针)     

      weak_ptr是一种不控制对象生命周期的弱引用指针(因为没有引用计数)。用来解决shared_ptr相互引用导致引用计数不能减为0。它不会增加引用计数,可以和shared_ptr之间相互转化,shared_ptr可以赋值给他(反之亦然),可以通过lock函数获取shared_ptr指针。

        weak_ptr没有*和->,因为它不共享指针,构造和析构不会造成引用计数的增减。作为一个纯粹的旁观者监测shared_ptr变化。weak_ptr还可以返回this指针和解决循环引用的问题

 1.基本用法

  1).weak_ptr使用

shared_ptr<int> s1(new int);
weak_ptr<int>   w1(s1);
cout<< w1.use_count()<<endl; //输出1

2). 通过expired()方法判断所观察资源是否已经释放

int main()
{
    shared_ptr<int> s1(new int);
    weak_ptr<int>   w1(s1);
    weak_ptr<int>   w2(move(w1));
    cout<< w1.use_count()<<endl;
    if(w1.expired())
        cout<<"weak_ptr invalid, Resources have been released"<<endl;
    else
        cout<<"weak_ptr valid, Resources is in use"<<endl;
    return 0;
}

通过move将w1指针移动到w2.w1释放。    

 3). 通过lock方法获取监视的shared_ptr

std::weak_ptr<int> ptr2; 
void f() 
{ 
    if(ptr2.expired())
    { 
        cout << "ptr2 invaild"<<endl; 
    }else
    {
        auto spt = ptr2.lock(); 
        cout << "ptr2, *spt = " << *spt << endl; 
    }
}

int main()
{
    { 
        auto ptr1 = make_shared<int>(42); 
        ptr2 = ptr1; 
        f(); 
    } //作用域结束,指针释放
    f();

    return 0;

        

  2. weak_ptr返回this指针

    在使用shared_ptr的时候是不能直接将this指针返回shared_ptr的,需要通过继承enable_shared_from_this,然后基类调用shared_from_this()返回指针。这是因为
enable_shared_from_this中有一个weak_ptr.这个weak_ptr通过lock观察this指针.将观察的shared_ptr返回。

3. 解决循环引用

    智能指针shared_ptr的出现可以使得同一资源被多个指针共享,并且保证共享资源只被释放一次,其内部使用计数器原理。但是两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。

看案例:

#include <string.h>
#include <unistd.h>
#include<iostream>
#include <memory>

using namespace std;

class B;

class A 
{
public:
    shared_ptr<B> pb_;  
    ~A()
    {
        cout<<"A delete"<<endl;
    }   
};

class B
{

public:
    shared_ptr<A> pa_;
    ~B()
    {
        cout<<"B delete"<<endl;
    }

};


int main()
{
    shared_ptr<B> pb(new B());
    shared_ptr<A> pa(new A());

    pb->pa_ = pa;
    pa->pb_ = pb;
    cout<<pb.use_count()<<endl;
    cout<<pa.use_count()<<endl;
    
    return 0;
}

 解决办法:

        weak_ptr对象指向shared_ptr对象时(反之亦然),不会增加shared_ptr中的引用计数。

        当然使用weak_ptr的时候需要注意访问对象的方法。

shared_ptr<B> pb_ -> weak_ptr<B> pb_;

weak_ptr使用注意事项:

    在使用wp前需要调用wp.expired()函数判断一下。避免对象已经释放问题

四:智能指针使用安全问题

  1. 注意多线程共享智能指针问题,后续补充

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

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

相关文章

耕地单目标语义分割实践——Deeplab3+语义分割

耕地单目标语义分割实践系列文章&#xff1a; [1*] 语义分割实践数据集制作—以Sentinel-2 MSI数据为例_doll &#xff5e;CJ的博客-CSDN博客 [2*] 耕地单目标语义分割实践——Pytorch网络过程实现理解_doll &#xff5e;CJ的博客-CSDN博客 [3*] 基于Pytorch的神经网络部分…

RocketMQ-(8-1)-EventBridge-EventBridge 核心概念

RocketMQ EventBridge 核心概念 理解EventBridge中的核心概念&#xff0c;能帮助我们更好的分析和使用EventBridge。本文重点介绍下EventBridge中包含的术语&#xff1a; EventSource&#xff1a;事件源。用于管理发送到EventBridge的事件&#xff0c;所有发送到EventBridge中…

【ES】elasticsearch8.3.3

这里仅实践操作并根据实际问题进行记录笔记。 运行 ES8 我们需要在自己的电脑上安装好 Docker Desktop。接着我们运行如下的命令&#xff1a;出现两个异常&#xff0c;一个是需要使用winpty因为我使用win的docker desktop&#xff0c;另外一个问题是docker启动elasticsearchE…

408考研-数据结构算法-顺序表

数组 如何创建数组 我们以 Java 中创建数组为例&#xff0c;创建语法如下 dataType[] arrName new dataType[size];dataType: 也就是我们数组中元素的数据类型arrName:即数组名size:即数组所能容纳的元素数量new: Java 语言中的关键词 假设我们要创建一个由 10 个元素的数…

基于SSM的在线挂号系统java医院预约管理 jsp源代码Mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目介绍 基于SSM的在线挂号系统 系统有2权限&#xff1a;前台…

基于SSM的服装商城系统【附源码】

【项目特色】 抽奖功能优惠劵功能物流信息功能 简介 基于SSM的服装商城系统 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;Spring、Mybaits、SpringMVC工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 前台功能:   注册、登录、退出、商品查询、商品列…

Oracle查询语句中做日期加减运算

在Oracle中&#xff0c;可以使用日期函数来实现日期的加减。 若想在日期上加上一定的天数&#xff0c;可以使用"INTERVAL"关键字。例如&#xff0c;如果要将一个日期加上3天&#xff0c;可以使用以下代码&#xff1a; SELECT SYSDATE INTERVAL 3 DAY FROM DUAL; …

【征稿信息】第四届先进材料和智能制造国际学术会议(ICAMIM2023)

第四届先进材料和智能制造国际学术会议&#xff08;ICAMIM2023) 2023 4th International Conference on Advanced Materials and Intelligent Manufacturing 2023年广州市“国际学术会议之都”建设项目— 第四届先进材料和智能制造国际学术会议&#xff08;ICAMIM2023)将于202…

徐涛政治导论:著作串连表格(重点掌握)

考试第一个选择题常考下面的表格&#xff08;很重要&#xff09;

使用vue-pdf出现的卡顿,空白,报错,浏览器崩溃解决办法

如果想直接知道解决办法&#xff0c;请翻到最下面 今天&#xff0c;接到了一个新的需求&#xff0c;我们公司的PDF展示卡住了&#xff0c;导致浏览器直接奔溃。我也刚来公司不久&#xff0c;就去看看是怎么发生的&#xff0c;公司前同事用的vue-pdf&#xff0c;刚开始以为是文…

基于鹈鹕算法优化的BP神经网络(预测应用) - 附代码

基于鹈鹕算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于鹈鹕算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.鹈鹕优化BP神经网络2.1 BP神经网络参数设置2.2 鹈鹕算法应用 4.测试结果&#xff1a;5.Matlab代码 摘要…

QT(8.31)加载资源文件,信号与槽机制

作业&#xff1a; 实现登录界面&#xff0c;设置账号为admin&#xff0c;密码为123456&#xff0c;登陆成功则退出当前界面&#xff0c;切换到其他界面&#xff0c;密码错误或者账号不匹配则清空账号密码输入框中的内容&#xff0c;并输出登录失败&#xff0c;点击取消则退出当…

AIGC爆火,拓世法宝平台上线,打造属于你的专属数字人!

在数字科技的风潮下&#xff0c;短视频已经成为人们日常生活中不可或缺的一部分。中国互联网络信息中心于8月28日发布的第52次《中国互联网络发展状况统计报告》报告显示&#xff0c;截至2023年6月&#xff0c;中国短视频用户已达10.26亿人。在这里面&#xff0c;80后、90后和0…

视觉SLAM与激光SLAM简单对比分析

总述 本文旨在梳理目前较为前沿的SLAM技术&#xff0c;包括激光和视觉&#xff0c;主要从精度和实时性两个方面对算法进系评价。 对于激光SLAM了解不深&#xff0c;后期需要补充相关算法的核心思想与算法框架。有问题请大佬们随时留言&#xff0c;我再改正。 0.1 视觉SLAM算…

vue使用qrcodejs2生成二维码

目录 概要 构建展示的vue组件qrcode.vue 组件的使用 概要 项目中用到需要展示二维码的样式&#xff0c;想到了qrcode 例如&#xff1a; 前提&#xff1a;安装包 npm install qrcodejs2 --save 构建展示的vue组件qrcode.vue <template><div style"width: …

使用spring自带的发布订阅来实现发布订阅

背景 公司的项目以前代码里面有存在使用spring自带发布订阅的代码&#xff0c;因此稍微学习一下如何使用&#xff0c;并了解一下这种实现方式的优缺点。 优点 实现方便&#xff0c;代码方面基本只需要定义消息体和消费者&#xff0c;适用于小型应用程序。不依赖外部中间件&a…

学习高级数据结构:探索平衡树与图的高级算法

文章目录 1. 平衡树&#xff1a;维护数据的平衡与高效性1.1 AVL 树&#xff1a;严格的平衡1.2 红黑树&#xff1a;近似平衡 2. 图的高级算法&#xff1a;建模复杂关系与优化2.1 最小生成树&#xff1a;寻找最优连接方式2.2 拓扑排序&#xff1a;解决依赖关系 拓展思考 &#x1…

el-select下拉多选框 el-select 设置默认值不可删除功能

Element3.0vue3.0 el-select下拉多选框 el-select 设置默认值不可删除功能 Element-UI是一款广泛使用的Vue.js组件库&#xff0c;其中El-Select下拉多选框组件在实际项目开发中经常被使用。然而&#xff0c;在Element 3.0版本中&#xff0c;El-Select下拉多选框默认值可被删除&…

降本56%!纵腾集团搭载OceanBase Cloud开启降本增效新篇章

近日&#xff0c;跨境电商物流领跑企业福建纵腾网络有限公司&#xff08;以下简称“纵腾集团”&#xff09;正式商用原生分布式数据库 OceanBase&#xff0c;为其下专业物流服务“云途物流”提供云数据库支撑服务。目前&#xff0c;已有两大关键业务系统全部接入 OceanBase Clo…

基于JAVAEE技术的ssm校园车辆管理系统源码和论文

基于JAVAEE技术的ssm校园车辆管理系统源码和论文105 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 1.选题背景和意义 背景&#xff1a; 随着第二次工业革命后&#xff0c;内燃机的发明与完善&#xff0c;解…