C++智能指针介绍

引言

为了充分利用RAII思想,C++ 11开始引入了智能指针,本文介绍RAII以及三种智能指针:

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

除此之外,本文还会介绍智能指针的常用创建方法:

  • std::make_unique
  • std::make_shared

RAII

RAII指的是Resources Acquisition Is Initialization,其是一种C++编程思想,指的是在初始化的时候就完成资源的分配,而在析构的时候自动释放资源。

C++中有很多RAII思想的体现:

  • 多线程中自动获取与释放锁对象std::unique_lock等;
  • 内存的自动申请与释放,智能指针;
  • 自动执行joinstd::jthread等。

RAII的目的是为了更好的组织代码,减少程序员犯错的可能。例如程序员可能忘记释放已经申请的内存或者锁,而利用RAII在对象析构的时候会自动进行资源的释放。

智能指针

智能指针是用于实现内存资源RAII的相关类型。

在我们需要在堆上进行内存申请的时候,我们往往会通过new关键字来进行内存的申请:

class Base {
public:
	int num{10};
    Base() {}
    ~Base() { std::cout << "~Base()" << std::endl; }
};
Base *b = new Base;
delete b;

而对于申请的内存我们需要通过delete进行释放以防止内存泄漏。

但是在实际开发过程中,我们很有可能忘记释放掉申请的内存,例如:

// memory leakage.
int test() {
    Base *b = new Base;
    if (!check()) { // do some check, but failed.
    	// delete b; // this may be forgotten easily.
        return -1;
    }
    delete b;
    return 0;
}

我们对于正常的情况记住了释放内存,但在出错的时候,可能就忘记释放内存了。

而智能指针能够有效的防止上面的情况发生。

std::unique_ptr

对于上面的代码我们完全可以使用std::unique_ptr替换原始指针:

int test()
{
    std::unique_ptr<Base> b(new Base);
    if (!check()) { // do some check, but failed.
        return -1;
    }
    return 0;
}

这样上面的代码不论如何只要在b析构的时候便会释放掉申请的内存,执行上面的代码可以看到成功输出~Base()

智能指针的使用方式与普通的指针一样,通过*->可以对指针进行解引用和获取成员变量的值:

std::unique_ptr<Base> b(new Base);
std::unique_ptr<int> p(new int);
*p = 10;
std::cout << *p << std::endl;
std::cout << b->num << std::endl;

通过智能指针也可以创建数组:

std::unique_ptr<int[]> p(new int[10]);
for (int i = 0; i < 10; i++) {
    p[i] = i;
}
for (int i = 0; i < 10; i++) {
    std::cout << p[i] << std::endl;
}

智能指针同样可以直接绑定到一个原始指针上面,不过需要注意的如果智能指针声明周期结束,那么原始的指针则变成了野指针:

int *p = new int{0};
{
    std::unique_ptr<int> up(p);
    std::cout << *up << std::endl;
}
// we cannot use p here, it's a dangling pointer.
// the dereference of a dangling pointer is a UB.

与指针相同,智能指针也能用来表现多态:

class Base {
public:
    Base() { std::cout << "Base()" << std::endl; }
    virtual ~Base() { std::cout << "~Base()" << std::endl; }
    virtual void test() { std::cout << "Base test()" << std::endl; }
};
class Derived : public Base {
public:
    void test() { std::cout << "Derived test()" << std::endl; }
};
std::unique_ptr<Base> base(new Derived);
base->test(); // "Derived test()"
return 0;

需要注意的是,不能有两个unique_ptr绑定了同一块地址上的内存(所以std::unique_ptr只支持移动语义,而不支持拷贝语义),同时注意不要让unique_ptr去绑定栈上的内存(当然如果非要这样做的话,也可以把默认的deleter给替换掉即可,但是这样并没有什么意义)。

第二个模板参数

智能指针也能接收第二个模板参数,其类型是一个可执行对象,同时接收一个指针作为参数,用于释放指针的资源。

默认的deleter:如果第一个模板参数类型不是数组类型(即第一个模板参数不含有[]),那么默认的deleter通过delete关键字进行释放内存;如果第第一个模板参数是数组类型(即第一个模板参数含有[])那么默认的deleter通过调用delete []进行内存释放。

由于默认deleter的行为,这也是为什么不能传入指向栈上内存的指针的原因(而且栈上的内存会自动释放,也不需要使用只能指针来管理)。

高级用法 自定义RAII

智能指针除了能够实现对于指针的智能管理之外,其同样可以对于任意的需要申请以及释放的资源进行智能管理。

这里给出官网的打开文件的例子:

void close_file(std::FILE* fp) {
    std::fclose(fp);
    std::cout << "File closed" << std::endl;
}
{
	using unique_file_t = std::unique_ptr<std::FILE, decltype(&close_file)>;
	// make sure there is demo.txt in current directory.
    // otherwise the fp is nullptr
	unique_file_t fp(std::fopen("demo.txt", "r"), &close_file);
} // here fp is finalized, so the close_file() will be called.

上面通过自定义deleter通过对打开文件的自动关闭。

std::shared_ptr

std::shared_ptr也是智能指针,其与std::unique_ptr的一个不同是:可以有多个std::shared_ptr与同一个地址进行绑定。其常常用于多线程。

std::shared_ptr中保存着一个引用计数,用来表示当前的地址绑定到了多少个std::shared_ptr对象上,每有一个指向相同地址的std::shared_ptr对象被创建(需要保证从一个std::shared_ptr拷贝过来),其引用计数便会增加1,当被析构时,其引用计数会减少1,而引用计数减少到0的时候,指向的资源便会被deleter释放(默认deleterstd::unique_ptr中相同)。

下面给出一个例子:

int *p = new int;
std::shared_ptr<int> sp1(p);
{
    // this is wrong, when we bind p with a shared_ptr, its ref_count is 1.
   	// so this will cause double free.
   	// only copy from a shared_ptr can make the ref_count increase correctly.
    // sdt::shared_ptr<int> sp2(p);
    std::shared_ptr<int> sp2(sp1);
    std::cout << sp2.use_count() << std::endl; // 2
    std::cout << sp1.use_count() << std::endl; // 2
}
std::cout << sp1.use_count() << std::endl; // 1

由于std::shared_ptr本来是为多线程设计的,因此其保证了其内部的函数均为线程安全的,也就是use_count等函数不会出现不一致的问题。但是对于指针指向的数据的操作在多线程中往往需要额外的手段实现同步。

std::weak_ptr

std::weak_ptr严格意义上来讲并不是一个指针,其更像是一种弱引用,其可以延长数据的生命周期。

std::weak_ptr通常通过一个std::shared_ptr对象创建而来或者通过一个std::weak_ptr对象拷贝而来。

当通过一个std::sahred_ptr对象创建而来的时候,其并不会增加引用计数,例如下面的例子:

std::shared_ptr<Base> sp(new Base);
std::weak_ptr<Base> wp = sp;
std::cout << sp.use_count() << std::endl; // 1
std::cout << wp.use_count() << std::endl; // 1

可以通过std::weak_ptr延长声明周期指的是std::weak_ptr::lock方法能够创建一个新的std::shared_ptr对象(如果内存还没被释放)此时引用计数均会增加1

 std::shared_ptr<Base> sp(new Base);
 std::weak_ptr<Base> wp = sp;
 std::shared_ptr<Base> newSp = wp.lock();
 std::cout << newSp.use_count() << std::endl; // 2
 std::cout << wp.use_count() << std::endl; // 2
 std::cout << sp.use_count() << std::endl; // 2

由于此时已经有新的std::shared_ptr产生,那么将原来而std::shared_ptr释放后,并不会释放资源:

sp.reset();
std::cout << newSp.use_count() << std::endl; // 1
std::cout << wp.use_count() << std::endl; // 1

而对于lock方法如果在lock的时候,资源已经被释放了,那么此时创建的std::shared_ptr对象与nullptr绑定,因此使用lock之后我们往往需要先判断资源是否已经在lock之前被释放:

std::shared_ptr<Base> sp(new Base);
std::weak_ptr<Base> wp = sp;
sp.reset(); // "~Base()"
std::shared_ptr<Base> newSp = wp.lock(); // newSp is bind with nullptr;
if (newSp != nullptr) {
    // do something...
}
std::cout << sp.use_count() << std::endl; // 0

正如lock函数的名字一样,其同样是线程安全的,其线程安全指的是:如果lock返回的std::shared_ptr对象并不是与nullptr绑定,那么保证此时资源没有被释放,如果返回的std::shared_ptrnullptr绑定,那么保证此时资源已经被释放。同样地,对于数据的访问依然需要通过锁或者其他手段实现同步。

更为方便的创建方式

在之前介绍的智能指针中还是需要使用到new关键字,但是却没有使用delete关键字,这很不符合RAII,于是有了这两个函数的实现,能够完全脱离new关键字进行智能指针的创建。

std::make_unique

C++ 14开始支持。

使用方法非常简单,只需要要通过模板参数传入类型和构造器参数:

class Base {
public:
	int num1;
	int num2;
	Base() = default;
	Base(int i, int j) : num1(i), num2(j) {}
};
std::unique_ptr<Base> up = std::make_unique<Base>(1, 2); // new Base(1, 2);
// create an array.
// only one parameter is OK, the parameter is the size of the array.
// make sure that the Base() constructor exists.
std::unique_ptr<Base[]> upArray = std::make_unique<Base[]>(3); // new Base[3];

std::make_shared

C++ 11开始支持。

该方法使用于std::make_unique一样,只是返回的是std::shared_ptr,故此处不在赘述。

One Funny Thing

std::make_uniqueC++ 14才开始支持,而std::make_sharedC++ 11就已经支持了。据说是因为当时作者给搞忘了。
在这里插入图片描述

参考

std::unique_ptr cppreference
std::shared_ptr cppreference
std::weak_ptr cppreference
std::make_unique cppreference
std::make_shared cppreference

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

珠海盈致wms管理软件在注塑行业的应用

wms管理软件在注塑行业的应用主要体现在以下几个方面&#xff1a; 实现物料精细化管理&#xff1a;WMS通过为每个原材料或成品进行条码标签&#xff0c;能够显示原材料和成品的详细信息&#xff0c;如产品名称、条码编号、生产日期、入库日期、供应商等。在入库、出库、库内管理…

three.js(二)

three.js&#xff08;二&#xff09; 参考前言正文简单开始(不使用任何框架)补充 粗略带过(使用Vue框架)细致讲解(比如我使用react框架)App.jsx 的进阶版 项目打包补充打包遇到的问题:原因:解决办法: 参考 https://threejs.org/docs/ 前言 上一集中,我们用到了three.js的一个…

Web漏洞分析-文件解析及上传(下)

随着互联网的迅速发展&#xff0c;网络安全问题变得日益复杂&#xff0c;而文件解析及上传漏洞成为攻击者们频繁攻击的热点之一。本文将深入研究文件解析及上传漏洞&#xff0c;通过对文件上传、Web容器IIS、命令执行、Nginx文件解析漏洞以及公猫任意文件上传等方面的细致分析&…

代驾系统开发:驶向未来的智能交通服务

随着科技的迅速发展&#xff0c;代驾系统的开发成为改善出行体验和提升交通服务智能化的重要一环。本文将聚焦于代驾系统开发的技术创新&#xff0c;为读者呈现其中涉及的一些令人振奋的技术代码。 1. 区块链技术的运用&#xff1a; 区块链技术被引入代驾系统&#xff0c;可…

尚海整装装修流程 揭秘装修行业的高标准服务

装修房子其实比较复杂&#xff0c;在这一复杂的过程中&#xff0c;一个合理且严密的装修流程显得尤为关键。错误的步骤安排不仅会造成资源的浪费&#xff0c;更有可能让人产生“弃屋重装”的冲动。尚海整装以精准的流程规划确保了装修的每一步都有序进行&#xff0c;轻松搞定装…

人工智能与VR技术

人工智能与虚拟现实技术&#xff08;VR&#xff09;的结合是当今科技领域中备受瞩目的话题。两者的结合不仅在娱乐、教育、医疗等领域展现出了巨大的潜力&#xff0c;而且在未来的发展趋势中也将具有重要意义。本文将从技术融合、应用场景和未来发展等方面探讨人工智能与虚拟现…

激荡思享 驱动增长 | 湾数联·湾董会走进竹云

12月12日&#xff0c;由湾区数字科技产业联盟&#xff08;GBADA&#xff09;、湾盟产业创新服务中心&#xff08;GBAIC&#xff09;、深圳竹云科技股份有限公司联合主办的“湾数联湾董会”第一期在深圳南山区创智云城圆满举办。本期湾董会以“竹云IDaaS数字身份为企业数字化转型…

select简单使用

语法 先大致看一下&#xff0c;后面都会讲&#xff0c;distinct用来去重&#xff0c;from 指明表名&#xff0c;where语句则用来控制查询条件&#xff0c;order by则用来对结果进行升序/降序排序&#xff0c;limit则用来分页。 SELECT [DISTINCT] {* | {column [, column] ..…

mysqlsh导入json,最终还得靠navicat导入json

工作需要将一个巨大的10G的json导入mysql数据库。 看到mysql官方有对json导入的支持。 如下&#xff1a; MySQL :: Import JSON to MySQL made easy with the MySQL Shell $ mysqlsh rootlocalhost:33300/test --import /path_to_file/zips.json Creating a session to root…

Linux——进程地址空间与进程控制

进程地址空间与进程控制 文章目录 进程地址空间与进程控制1. 进程地址空间1.1 进程地址空间的引入1.1 进程地址空间的特点1.2 页表1.3 C/C的地址1.4 进程地址空间 页表的优势 2. 进程控制2.1 进程创建2.1.1 写时拷贝 2.2 进程终止2.2.1 进程退出码2.2.2 异常信号码2.2.3 errno…

阿里巴巴旗下的 AI 平台 AIHub 提供多种免费的图片 AI 在线工具

顽兔抠图&#xff1a;一键去除图像背景的 AI 工具&#xff0c;支持图片和视频。顽兔抠图 淘宝网(Taobao.com)作为专业的购物网站拥有全球时尚前沿的消费者购物集市,100%认证网上商城及超值二手商品区&#xff0c;同时购物安全&#xff0c;产品丰富&#xff0c;应有尽有,任你选购…

解决固定资产盘点问题,易点易动来帮忙!

固定资产盘点是企业管理中不可或缺的环节&#xff0c;然而&#xff0c;很多企业在固定资产盘点方面面临一系列问题&#xff1a; 盘点过程繁琐&#xff1a;传统的手动盘点方式需要耗费大量人力和时间&#xff0c;容易出现疏漏和错误&#xff0c;效率低下&#xff1b; 数据记录不…

uniapp 之 图片 视频 文件上传

<view class"" style"padding: 24rpx 0"><text>相关资料 <text class"fs-26 color-666">&#xff08;图片、视频、文档不超过9个&#xff09;</text> </text><view class"flex align-center" style&…

安全算法(二):共享密钥加密、公开密钥加密、混合加密和迪菲-赫尔曼密钥交换

安全算法&#xff08;二&#xff09;&#xff1a;共享密钥加密、公开密钥加密、混合加密和迪菲-赫尔曼密钥交换 本章介绍了共享密钥加密、公开密钥加密&#xff0c;和两种加密方法混合使用的混合加密方法&#xff1b;最后介绍了迪菲-赫尔曼密钥交换。 加密数据的方法可以分为…

Day09 Liunx高级系统设计11-数据库1

MySQL 简介 数据库DB 数据库&#xff08; DataBase &#xff0c; DB &#xff09;从本质上讲就是一个文件系统&#xff0c;它能够将数据有组织地集合在一起&#xff0c;按照一定的规则长期存储到计算机的磁盘中&#xff0c;并且能够供多个用户共享和使用&#xff0c;同时&…

Leetcode—459.重复的子字符串【简单】

2023每日刷题&#xff08;五十九&#xff09; Leetcode—459.重复的子字符串 算法思想 巧解的算法思想 实现代码 从第一个位置开始到s.size()之前&#xff0c;看s字符串是否是ss的子串 class Solution { public:bool repeatedSubstringPattern(string s) {return (s s).fin…

西南科技大学数字电子技术实验七(4行串行累加器设计及FPGA实现)FPGA部分

一、实验目的 1、掌握基于Verilog语言的diamond工具设计全流程。 2、熟悉、应用Verilog HDL描述数字电路。 3、掌握Verilog HDL的组合和时序逻辑电路的设计方法。 4、掌握“小脚丫”开发板的使用方法。 二、实验原理 三、程序清单&#xff08;每条语句必须包括注释或在开发…

【产品应用】一体化伺服电机在TO全自动封焊机中的应用

随着科技的飞速发展&#xff0c;自动化设备在各行各业中的应用越来越广泛。在电子制造领域&#xff0c;封焊机是关键设备之一&#xff0c;其性能直接影响产品的质量和产量。近年来&#xff0c;一体化伺服电机在TO全自动封焊机中的应用逐渐受到关注。本文将详细介绍一体化伺服电…

用Rust帮Python加加速

背景 长期以来,Python由于易上手,有GC且生态强大等特点被广泛使用,可是渐渐的人们也发现了它的不足,解释型语言的运行速度终究比不过编译型,况且由于Python设计时的动态数据类型一切皆对象(内存都分配在堆上)等思想,也导致了运行速度缓慢. 随着实时性要求的不断提升,在一些计…

Windows下使用CMake编译lua

Lua 是一个功能强大、高效、轻量级、可嵌入的脚本语言。它支持程序编程、面向对象程序设计、函数式编程、数据驱动编程和数据描述。 Lua的官方网站上只提供了源码&#xff0c;需要使用Make进行编译&#xff0c;具体的编译方法为 curl -R -O http://www.lua.org/ftp/lua-5.4.6.…