C++——智能指针简介

C++实现单例模式(包括采用C++11中的智能指针) 对于设计模式来说,以前只是看过基础的理论,很多都没有实现和使用过。这段时间看到了别人C++代码中使用了单例模式,发现了很多新的东西,特此总结记录一下。说话比较啰嗦,希望由浅入深,帮助大家理解!     单例模式,顾名思义,即一个类只有一个实例对象。C++一般的方法是将构造函数、拷贝构造函数以及赋值操作符函数声明为private级别,从而阻止用户实例化一个类。那么,如何才能获得该类的对象呢?这时, 阅读详情

##一、什么是对象所有权?
  在接触智能指针之前首先要理解对象的所有权是什么,在这之前我们总是用new和delete来进行内存的申请与释放,在这种堆内存分配的方式中,要遵守一个很基本的原则——谁创建谁销毁原则,简单地举个例子,类foo构造函数中中通过new申请了一个数组,那么就要在类foo的析构函数中delete这个数组,而对象的所有权指的就是谁负责delete这个对象的关系。

根据几种智能指针的用途来分,对象的所有权可以分为独占所有权、分享所有权和弱引用。现在来一一介绍它们:
  
  独占所有权:假设Dad拥有Son的独占所有权,那么Son就必须由Dad来delete,再从字面上看,独占意味如果有另一个对象OldWang想要持有Son的话,就必须让Dad放弃对Son的所有权,此所谓独占,亦即不可分享。
  
  分享所有权:假设Dad拥有PS4的分享所有权,那么由最后一个持有PS4的对象来对其进行delete,也就是说,如果这个时候Son也想要持有PS4,Dad不必放弃自己的所有权,而是把所有权分享给Son,而如果Dad被销毁(生命周期结束、被释放等),那PS4就在Son被销毁时被释放,反之如果Son先于Dad被销毁,那么PS4就由Dad来释放。
  
  弱引用:假设AccountThief对Account有弱引用的话,那么AccountThief可以使用Account,但是AccountThief不负责释放Account,如果Account已经被拥有其所有权的对象(比如AccountOwner)释放后,AccountThief还想继续使用Account的时候就会取得一个nullptr(nullptr勉强可以当做NULL来看,前者是关键字,后者是宏定义)。
##二、什么是智能指针?
  智能指针是行为类似于指针的模板类对象,可以对其进行解引用*,指向结构体成员->等操作但是不能进行指针算术运算比如自加++、自减--等,因为指针算术运算实现上是根据指针所指空间大小来进行内存位置上的偏移,而智能指针是类对象,这种运算是没有意义的。当然它具有智能的地方,智能指针最为方便的就是能自动管理堆内存,无需关心何时收回已分配的内存。这么一听是不是觉得它很强?但先别高兴的太早,也有一些需要注意的地方,所有事情都有两面性,了解智能指针后我们将会提到一些需要注意的地方。
  然后根据以上介绍的所有权类型,一一对应地,我们有unique_ptr、shared_ptr、weak_ptr,还有被时代抛弃的auto_ptr(稍后也会谈谈它为什么被抛弃)。
##三、怎么使用智能指针?
###  1、怎么使用unique_ptr?
  在使用unique_ptr的时候,首先要知道它的make函数make_unique(),make_unique()可以构造一个类对象并返回指向它的unique_ptr,例如make_unique<int>(),就会返回一个指向int的unique_ptr,而像make_unique<int>(1)一样带有参数就会返回一个指向值为1的int的unique_ptr,实际上就上面两个就相当于先通过int{}和int{1}创建对象并构造指向它们的unique_ptr。那么对于得到的unique_ptr,我们可以像使用指针一样去使用。
  现在我们有Dad对象是指向Son的unique_ptr,如果要Dad放弃对这个对象的所有权,就需要调用Dad.release()来将所有权进行释放,这个函数将会返回指向Son的普通指针Son*,现在就可以用这个不属于任何人的普通指针来构造一个新的unique_ptr。如果这个sonPtr是假的(这个指针没有管理对象,也就是说Son是不存在的),那么调用release()的时候就会返回nullptr。
  先来看一段代码:

#include <iostream>
#include <memory>

using namespace std;  //不提倡

int main() {
  unique_ptr<string> Dad{make_unique<string>("Son")};
  unique_ptr<string> OldWang{Dad};
}

根本就过不了编译!试图把unique_ptr赋值给另一个unique_ptr在编译时就会出错!
  说一句题外话:其具体实现方法是重载了unique_ptr的拷贝构造函数,unique_ptr的定义中有一句:
  unique_ptr(const unique_ptr&) = delete;
  所以调用它的时候是会出错的。
  
  再来看一段代码:

#include <iostream>
#include <memory>

using namespace std;  //不提倡

int main() {
  unique_ptr<string> Dad{make_unique<string>("Son")};
  cout << "Dad owns " << (Dad ? *Dad : "nothing") << endl;
  unique_ptr<string> OldWang{Dad.release()};
  cout << "Dad owns " << (Dad ? *Dad : "nothing") << endl;
  cout << "OldWang owns " << (OldWang ? *OldWang : "nothing") << endl;
}

上面有一个用法,就是直接把unique_ptr转换成bool值来判断其是否为空。
  那么这段程序的输出,不出所料的是:

Dad owns Son
Dad owns nothing
OldWang owns Son

那么如果这样呢?(差别仅仅是Dad为nullptr)

#include <iostream>
#include <memory>

using namespace std;  //不提倡

int main() {
  unique_ptr<string> Dad{nullptr};
  cout << "Dad owns " << (Dad ? *Dad : "nothing") << endl;
  unique_ptr<string> OldWang{Dad.release()};
  cout << "Dad owns " << (Dad ? *Dad : "nothing") << endl;
  cout << "OldWang owns " << (OldWang ? *OldWang : "nothing") << endl;
}

那么调用Dad.release()后就得到了一个空指针。
  显然输出将会变成:

Dad owns nothing
Dad owns nothing
OldWang owns nothing

###  2、怎么使用shared_ptr?
  接下来让我们看shared_ptr,了解了unique_ptr之后,shared_ptr的使用就显得不陌生了,具体使用时,同样需要用到make函数make_shared(),shared_ptr和unique_ptr的不同之处它在于是可以共享的,它可以赋值给其他shared_ptr,分享所有权。
  看下面这段代码:

#include <iostream>
#include <memory>

using namespace std;

int main() {
  shared_ptr<string> Dad{make_shared<string>("PS4")};
  shared_ptr<string> Son{nullptr};
  
  cout << "Dad owns " << (Dad ? *Dad : "nothing") << endl;
  cout << "Son owns " << (Son ? *Son : "nothing") << endl;
  cout << endl;

  Son = Dad;
  cout << "Dad owns " << (Dad ? *Dad : "nothing") << endl;
  cout << "Son owns " << (Son ? *Son : "nothing") << endl;
  cout << endl;
  
  Dad = nullptr;
  cout << "Dad owns " << (Dad ? *Dad : "nothing") << endl;
  cout << "Son owns " << (Son ? *Son : "nothing") << endl;
}

这段代码的输出会是:

Dad owns PS4
Son owns nothing

Dad owns PS4
Son owns PS4

Dad owns nothing
Son owns PS4

可以看出,shared_ptr的用法和unique_ptr的用法比较相似,只不过可以共享对指向对象的所有权而已。
  如果有多个shared_ptr指向某对象,则在最后一个指针过期时才释放该对象,这个功能在实现的时候运用到了引用计数,即跟踪引用特定对象的智能指针数,例如:赋值给新指针时,计数将+1,指针过期时,计数将-1,当计数为0时,该特定对象将会被delete。
###  3、怎么使用weak_ptr?
  我们需要从shared_ptr构造一个weak_ptr,也就是说有weak_ptr就肯定有shared_ptr,如果要使用weak_ptr所指向的对象,我们需要调用lock()函数,这个函数会返回所指对象的shared_ptr(在对象被销毁后就会得到空的share_ptr)。
  weak_ptr还有expired()函数来取得它所指向的对象是否还存在的bool值,expired意为期满的,所以若返回值为真,则所指对象已被销毁,反之所指对象仍存在。这个函数存在的意义在于如果仅仅是为了判断对象是否健在,那么不需要调用lock()函数。
  来看下面这段代码:

#include <iostream>
#include <memory>

using namespace std;  //不提倡

int main() {
  shared_ptr<string> AccountOwner{make_shared<string>("Account")};
  weak_ptr<string> AccountThief{AccountOwner};

  cout << "AccountOwner owns "
       << (AccountOwner ? *AccountOwner : "nothing") << endl;
  cout << "AccountThief can use "
       << (!AccountThief.expired() ? *AccountThief.lock() : "nothing") << endl;
  cout << endl;

  AccountOwner = nullptr;
  cout << "AccountOwner owns "
       << (AccountOwner ? *AccountOwner : "nothing") << endl;
  cout << "AccountThief can use "
       << (!AccountThief.expired() ? *AccountThief.lock() : "nothing") << endl;
}

这段代码的输出将会是:

AccountOwner owns Account
AccountThief can use Account

AccountOwner owns nothing
AccountThief can use nothing

weak_ptr的用法可以从这段代码中窥见一斑。
###  4、如何选取智能指针?
  上面介绍了三种智能指针,那么什么时候使用哪种呢?先来看一下shared_ptr和unique_ptr的使用场景,weak_ptr稍后再谈。

  1. 如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:
  • 有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大和最小(需要被赋值指向特定元素)。
  • 两个对象都包含指向第三个对象的指针。
  • STL容器包含指针。
  • ……
  1. 如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr,还有如果某函数使用new分配了内存,并返回指向该内存的指针,那么返回一个unique_ptr会是一个很好的选择。

有兴趣可以了解一下单例模式,并且想想其中的instance需要用哪种智能指针?

##四、auto_ptr为什么被抛弃了?
  首先来看一下auto_ptr的概念,auto_ptr是在C++11标准前使用的智能指针,虽然也建立了所有权的概念,但是定位不够明确,一个auto_ptr(例如oldPtr)能够赋值给其他auto_ptr(例如newPtr),但是oldPtr对指向对象(例如Object)的所有权将被newPtr剥夺,虽然这将避免oldPtr和newPtr各自调用一次Object的析构函数,但是再次使用oldPtr的时候会导致难以预料的结果,因为它不再指向有效的数据。这样看看,auto_ptr是不是同时具有了shared_ptr的赋值功能,以及unique_ptr对对象的独占所有权?所以使用不当会导致各种问题,有的时候还会难以发现。因此使用unique_ptr比使用auto_ptr更加安全,在编译时就可以避免非法的赋值操作,如果想要分享所有权,那很自然的就应该使用shared_ptr。
  
##五、智能指针注意事项

  • 我们不能把并不指向堆内存的指针赋值给智能指针,试想delete一个指向栈内存的指针会发生什么?智能指针还没有智能到自动分辨堆上对象和栈上对象。
  • unique_ptr和shared_ptr都有get()函数,能在不放弃所有权的情况下返回所指向对象的普通指针。
  • unique_ptr和shared_ptr还有reset()函数,效果相当于把nullptr赋值给它们。
  • 智能指针的内存泄漏:千万不要以为智能指针就不会导致内存泄漏了。现在我们来看一段代码:
#include <memory>

using namespace std;  //不提倡

class foo {
 public:
  shared_ptr<foo> father;
};

int main() {
  shared_ptr<foo> a{make_shared<foo>()};
  shared_ptr<foo> b{make_shared<foo>()};
  a->father = b;
  b->father = a;
}

这种情况下两个对象都分别被两个shared_ptr所指,一个是a和b->father,一个是b和a->father,当函数执行完以后,虽然a和b被析构,但是原来的a->father和b->father(对象自身含有的指针)仍然指着这两个对象,也就是说在这种情况下,一个对象的智能指针通过某种路径的一连串智能指针最终指向了自身,最终构成了一个环,这个对象就永远不会被自动释放了,这就是智能指针会造成的内存泄漏,那么为了避免这种情况,就轮到了我们的weak_ptr出场了。将上述类foo中的father替换成weak_ptr类型,就可以既实现相同的效果(当然取得对象的时候要用lock()),又避免了内存泄漏。
  这种误用导致的问题,在链表中比较常见,比如双向链表、循环链表等等,又比如说需要记录父节点的树,在这些情况下,会出现某对象内指针指向的对象内的指针指向自己的情况,使用shared_ptr就会引发内存泄漏,这是要务必小心的。

  • 不要把智能指针作为节省delete语句的工具,它们本身就是用来表示所有权关系的,使用的时候还是要从所有权的角度进行分析。

##六、总结
  智能指针给程序设计带来了极大的方便,使得**“别忘记delete”**不再那么困扰程序员,并且解决了某些场景下不知如何安放delete的问题,使得程序员能够高效而安全地管理堆内存,今后我亦会尝试使用智能指针,在此共勉。

C++智能指针简介 这篇文章将为大家带来智能指针,很多人都听过指针但是智能指针对于大家来说可能会很陌生。智能指针使用起来是非常方便的,有了智能指针我们就可以避免动态内存申请时出现内存泄漏等问题了。大家下去多理解一下智能指针,这个在以后的工程开发当中还是很有用的,有什么地方不懂的大家可以留言或者私信。 阅读详情

相关推荐

MPU6050数据融合避坑指南:当欧拉角遇到万向节死锁,我们该如何处理?

本文深入探讨了MPU6050在姿态解算中遇到的欧拉角万向节死锁问题,提供了四元数解决方案及工程实践策略。通过分析死锁现象的本质,介绍四元数的优势与实现方法,并给出必须使用欧拉角时的三种应对方案,帮助开发者有效规避数据跳变和解算失效问题。

weixin_30415113的博客 541

C++第十一节——单例模式 C++11 智能指针 异常 有关讲述

本节内容,我们要介绍四个模块的内容。按照顺序来,分别是 单例模式、C++11、异常和智能指针。 目录 单例模式 要求设计一个类,只能让其创建在堆上。 你实现一个类,要求该类只能在栈上去创建 设计一个函数,要求防止拷贝构造 单例模式: 饿汉模式 懒汉模式 懒汉模式和饿汉模式的对比 C++11 C++11简介 统一的列表初始化 声明类型 auto类型 decltype nullptr 范围for循环 STL的新内容 右值引用和移动语义 左值引用和右值引用比较 .

jxwd的博客 4120

linux 内核vxlan收发包流程

1.vxlan口收包处理流程 前面几章详细介绍了vxlan接口的使用,而且已经知道vxlan是 MAC IN UDP中的封装,因此,在解封装之前,一切按照原有流程走,在此复习一下内核收发包流程(驱动层的数据处理这次不再解析,直接从__netif_receive_skb_core开始) #物理网卡处理中断,触发softirq i40e_intr └─napi_schedule_irqoff #i40e softirq把包放到per cpu backlog,触发softirq i40e_napi_p

魏言华的博客 3826

单例模式之C++实现,智能指针,线程安全版本

单例模式

m0_72035301的博客 427

防火墙——双机热备理论讲解

本端和对端协商失败。有可能是因为本端和对端设备的软件版本不一致、某端配置错误、对端设备的心跳接口状态为Down、HRP源或目的端口号被修改、或者底层链路不通等原因导致。会话表、SeverMap表、IP监控表、分片缓存表、GTP表、黑名单、PAT方式端口映射表、NO-PAT方式地址映射表。两台设备一主一备,正常情况下业务流量由主设备处理,当主设备故障时,业务流量平滑切换到备用设备进行处理,业务不中断。开启自动备份后,当主用设备配置了一条可备份的命令或产生了可备份的会话表状态时,会直接备份到备用设备上。

m0_49864110的博客 2万+

深入解析ISO12233标准:SFR算法从理论到代码实现

本文深入解析ISO12233标准中的SFR算法,从理论到代码实现全面介绍如何量化成像系统的解析力。通过e-SFR算法测量边缘扩散函数(ESF)和空间频率响应(SFR),结合光学原理和实际代码示例,帮助开发者准确评估影像质量。文章包含关键步骤的Python实现和工程优化技巧,适用于相机测试、镜头评估等应用场景。

weixin_29200485的博客 154

C++ 单例模式智能指针实现

单例模式

春天花会开的专栏 2019

【QGIS】利用“创建网格”功能实现区域等距分割与坐标转换实战

本文详细介绍了如何在QGIS中使用“创建网格”工具实现区域等距分割。通过实战演示,将北京市行政区划切割为150米正方形网格,并重点讲解了关键步骤:选择投影坐标系EPSG:3857以确保网格尺寸精确,以及将结果转换为常用的WGS84经纬度坐标。文章还涵盖了进阶技巧与常见问题解决方案,帮助用户高效完成空间网格化分析任务。

weixin_29232507的博客 95

C++ 智能指针简介

智能指针有时需要将其管理的对象的所有权转移给其它的智能指针,使得多个智能指针管理同一个对象,比如C++ STL中的shared_ptr支持多个智能指针管理同一个对象。这个时候智能指针就需要知道其引用的对象总共有多少个智能指针在引用在它,也就是说智能指针所管理的对象总共有多少个所有者,我们称之为引用计数(Reference Counting),因为智能指针在准备释放所引用的对象时,如果有其他的智能指针同时在引用这个对象时,则不能释放,而只能将引用计数减一。

Doublelv 的博客专栏。 3468

Flotherm XT新手必看:5分钟搞定自然对流热仿真(附案例文件)

本文为Flotherm XT新手提供了自然对流热仿真的快速入门指南,通过详细的步骤解析和实用技巧,帮助用户在5分钟内完成从建模到后处理的全流程。重点介绍了环境设置、几何建模、材料属性、网格划分等关键环节,并附有案例文件供参考学习,助力工程师快速掌握热仿真技术。

weixin_30839881的博客 620

数字信号处理----数字上变频和下变频都FPGA实现

** 数字信号处理基础----信号下变频和解调 一、 任务介绍 数字上变频器(DUC)和数字下变频器(DDC)广泛应用于通信系统,用于信号采样速率的转换。当信号从基带转换至中频( IF )带,需要使用数字上变频器。而数字下变频器是用于将信号从中频( IF )带转换为基带。DUC和DDC通常包括使用混频器进行频率转换,此外还有采样率转换。DUC或DDC的结构主要取决于所需要的转换率。例如,WiMAX (全球互通微波接入)系统典型的转换率为8—10阶。对于如此低的转换率,DUC和DDC只需采用FIR滤波器架构。

Born_toward的博客 3万+

【C++入门到精通】智能指针 shared_ptr 简介及C++模拟实现 [ C++入门 ]

shared_ptr 是C++中的智能指针类,通过引用计数机制管理堆内存对象的生命周期,并使用原子操作确保引用计数的线程安全性。它支持拷贝构造和赋值运算符重载,可以安全地共享指向同一块堆内存的对象。此外,shared_ptr提供了方便的访问和操作接口,是一种方便而安全的资源管理工具

Yawesh的博客 3688

G120变频器抱闸逻辑在提升机上应用

一、前言: 对于提升机、起重机、堆垛机、升降机等提升机构,负载为位能性负载,在设备启动和停止阶段,由于重物作用,可能会出现“溜车”现象、控制变频器与电机抱闸时许可以解决此问题 使用具有矢量控制功能的变频器 二、参数设置 抱闸控制时序: 启动: 1、变频器接收到启动命令时-----输出电流对电机进行励磁,经过P346电机励磁时间(==丹佛斯 设置启动电流延时0.5S)------达到一定的转矩 2、变频器输出开抱闸命令,经过P1216延时打开抱闸时间(==PLC延时控制打开抱闸) 注意:抱闸

fenglingxicao的博客 1万+

C++ 之 【智能指针的简介】

weak_ptr不参与资源管理(不拥有资源,不控制生命周期)。weak_ptr不是 RAII(资源获取即初始化) 的实现,因为它不负责释放资源。weak_ptr不直接参与引用计数(不增加强引用计数,但通过弱引用计数辅助 shared_ptr 的控制块管理)。weak_ptr提供类似指针的访问行为(需通过 lock() 获取 shared_ptr 后解引用)。public::_ptr(ptr){}//sp1(sp2){}{}//1.指向同一块资源的指针没必要赋值。

zl_dfq的博客 1121

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

简介C++中的智能指针

简介C++中的智能指针 本文主要介绍C++标准中的3种智能指针: unique_ptr, shared_ptr, weak_ptr 至于早期的 auto_ptr (在C++11标准中被列为 deprecated) 和 boost::scoped_ptr (不属于C++标准),不在本文范围内。 智能指针的原理及RAII,属于比较基础的内容,不再多说。 要使用这3种智能指针,必须包含头文件 <memory>. 1. unique_ptr 1.1 常用的构造方法与赋值操作符 顾名思义, unique

FinixLei的专栏 (https://github.com/FinixLei) 940

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

c++智能指针的简介

C++智能指针是行为类似于指针的类对象。它使用设计模式中的代理模式,代理了原始“裸”指针的行为,为指针添加了更多更有用的特性。        C++引入异常机制后,智能指针由一种技巧升级为一种非常重要的技术,因为如果没有智能指针,程序员必须保证new对象能在正确的时机delete,四处编写异常捕获代码释放资源,而智能指针则可以在退出作用域时——不管是正常离开或是因异常离开——总调用delet

点滴的岁月 500

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

C++智能指针的简介

      内存泄露是C++程序员都头疼的大问题。C++缺乏像JAVA、C#一样,拥有GC这么一项有利的武器,它将内存管理的部分权限交给了程序员。虽然GC的存在节约了开发、排错的时间与成本,但是C++为了追求运行速度而20年来坚决不予补充进其标准。(题外话:C++通过加大开发难度去换取执行速度的做法,在现在看来不知是否能给与正面的评价,还是留给将来再说吧。)  从此,在堆上申请了内存忘了释放、所造...

mxdxm8899的专栏 87

【C++入门到精通】智能指针 auto_ptr、unique_ptr简介及C++模拟实现 [ C++入门 ]

这篇文章介绍了 C++ 中的两种智能指针:std::auto_ptr和std::unique_ptr。首先,文章简要介绍了它们的作用和使用示例。然后,对于std::auto_ptr,文章展示了一个简单的模拟实现,解释了每个函数的作用和实现原理。接着,文章介绍了更为严格的 std::unique_ptr,包括其特点、使用示例和模拟实现。对于模拟实现,文章详细解释了如何防止拷贝和赋值,并提供了两种不同的思路。整篇文章内容清晰明了,对于想要学习 C++ 动态内存管理的读者具有很好的参考价值。

Yawesh的博客 2727
上一篇: C++——迭代器简介
下一篇: C++——引用简介
Cipher丶海
博客等级 码龄10年 4粉丝 5原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值