C++智能指针介绍

news2024/11/16 7:23:37

引言

为了充分利用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对象被创建(需要保证从一个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

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

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

相关文章

微信小程序:用map()将对象数组中的某一项组合成新数组

使用分析 使用map()方法来遍历 info 数组中的每个元素&#xff0c;并整合每一个对象中的某一项进行新数组的重组 效果展示 这里是查询对象数组中的全部name值 原始数据 提取出name的数组 核心代码 var infos items.map(item > item.name); 完整代码&#xff08;用微信小程…

iOS按钮控件UIButton使用

1.在故事板中添加按钮控件,步聚如下: 同时按钮Shift+Commad+L在出现在控件库中选择Button并拖入View Controller Scene中 将控件与变量btnSelect关联 关联后空心变实心 如何关联?直接到属性窗口拖按钮变量到控件上,出现一条线,然后松开,这样就关联成功了 关联成功后属性窗口…

Redis HyperLogLog 数据结构模型统计

HyperLogLog HyperLogLog 不是一种新的数据结构 &#xff0c; 本质上是字符串类型。 是一种基数算法。 通过 HyperLogLog 可以节省内存空间&#xff0c;并完成独立总数的统计。 HyperLogLog 数据结构可用于仅使用少量恒定内存来计算集合中的唯一元素&#xff0c;具体而言&…

Web开发:VS2022列表导出CSV中文乱码问题(已解决)

目录 一、问题重现 二、解决方案 1.新建一个EXCEL文档 2.点击数据-点击导入&#xff08;生成的文件&#xff09;-设置中文格式 一、问题重现 使用VS2022 DEBUG导出列表时&#xff0c;打开CSV文件发现中文乱码 二、解决方案 1.新建一个EXCEL文档 2.点击数据-点击导入&…

新手选电视盒子什么牌子好?内行分享最新电视盒子排名

新手们在面对众多品牌和机型时难免不知道如何挑选电视盒子&#xff0c;电视盒子的品质良莠不齐&#xff0c;究竟电视盒子什么牌子好&#xff1f;我身为从业人员&#xff0c;身边朋友在挑选电视盒子时都会咨询我的意见&#xff0c;我特意整理了业内最新发布的热门电视盒子排名TO…

选择销售技巧培训机构注意事项

选择销售技巧培训机构注意事项 随着市场竞争的日益激烈&#xff0c;销售技巧对于企业的成功至关重要。为了提升销售团队的技能&#xff0c;许多企业选择投资于销售技巧培训机构。然而&#xff0c;在选择培训机构时&#xff0c;有几个关键因素需要考虑。本文将介绍选择销售技巧…

亚马逊云科技re_Invent 2023产品体验:亚马逊云科技产品应用实践 王炸产品Amazon Q,你的AI助手

本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 亚马逊云科技开发者社区, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 意料之中 2023年9月25日&#xff0c;亚马逊宣布与 Anthropic 正式展开战略合作&#x…

详解接口测试

目录 什么是接口&#xff1f; 接口协议的类型 接口测试是什么 HTTP接口的测试用例设计 HTTP接口的测试方法 什么是接口&#xff1f; 在面向对象编程中&#xff0c;接口是一个抽象的概念&#xff0c;用于定义类应该具有的方法和属性。一个类可以实现一个或多个接口&#xf…

如何解决掉你的u盘装不进去文件大小过大的文件

目录 前言1.解决方案2.原因2.1查看自己u盘格式2.2不同格式2.3分配单元大小作用 &#x1f44d; 点赞&#xff0c;你的认可是我创作的动力&#xff01; ⭐️ 收藏&#xff0c;你的青睐是我努力的方向&#xff01; ✏️ 评论&#xff0c;你的意见是我进步的财富&#xff01; 前言…

Ubuntu环境下使用GDB调试C语言项目

1. 安装gdb //终端输入 sudo apt-get install gdb 2. 启动gdb gdb GDB常用命令大全&#xff0c;参考此篇博客 使用GDB调试C项目中的makefile 1.在内核配置中启用调试信息&#xff1a; 在内核配置中&#xff0c;确保启用了调试信息。可以通过以下步骤来配置内核&#xff1…

uniapp 蓝牙小程序-兼容安卓和iOS

withTimeout方法可以在搜寻设备时等待指定的秒数&#xff0c;如果30秒内未搜索到则取消搜索 /*** 超时控制函数* param {Promise} promise 回调函数* param {number} timeout 超时时间, 默认10s*/ export function withTimeout(promise, timeout 10000) {let timeoutEvent …

FFmpeg的AVFilter框架总成AVFilter-AVFilterContext

毫无疑问&#xff0c;还是和前面的一样一个context和一个包含有回调函数指针的插件结构体&#xff0c;想要实现自己的插件&#xff0c;主要实现里面的回调函数就可以了&#xff0c;当然&#xff0c;AVFilter比其它模块稍微复杂一点还要牵扯到其它一些辅助模块&#xff0c;在其它…

华为OD试题六(数据最节约的备份方法、TLV解码)

1. 数据最节约的备份方法 题目描述&#xff1a; 有若干个文件&#xff0c;使用刻录光盘的方式进行备份&#xff0c;假设每张光盘的容量是500MB&#xff0c;求 使用光盘最少的文件分布方式 所有文件的大小都是整数的MB&#xff0c;且不超过500MB&#xff1b;文件不能分割、分卷…

elementui select中添加新增标签

<el-select v-model"ruleForm.eventType" :placeholder"请选择事件类型&#xff0c;可手动添加" ref"template" clearable visible-change"(v) > visibleChange(v, template)"><el-option v-for"item in eventTypeOp…

复制粘贴——QT实现原理

复制粘贴——QT实现原理 QT 剪贴板相关类 QClipboard 对外通用的剪贴板类&#xff0c;一般通过QGuiApplication::clipboard() 来获取对应的剪贴板实例。 // qtbase/src/gui/kernel/qclipboard.h class Q_GUI_EXPORT QClipboard : public QObject {Q_OBJECT private:explici…

华为OD试题五(数列描述、矩阵最大值、数据分类)

1. 数列描述 示例代码&#xff1a; # 核心 从第一项 推 第N项目 # 第一项 a0 1 # 推到 第N项 N 4 def fun(a0):# 计算每一项的具体值result left 0cursor 0while cursor < len(a0):if a0[cursor] ! a0[left]:count cursor -leftresult "{}{}".format(str(…

2.2 模型基础

建模流程 作业 这次搞了10天左右终于把作业做完了。 先是去学习了下如何建模->然后将模型导入Substance Painter里绘制贴图->最后导入到unity中&#xff08;虽然最后效果很差&#xff09;&#xff0c;但是回过头来看整个过程学习到了次时代美术的工作流&#xff0c;思考…

智慧公交:提高城市出行效率的数字化之路

随着城市化进程的不断加速&#xff0c;公共交通成为人们日常出行的主要方式之一。为了提高公共交通的效率和服务质量&#xff0c;智慧公交应运而生。智慧公交是一种基于物联网、大数据、人工智能等技术&#xff0c;对公共交通进行数字化、智能化改造的新型公共交通系统。 以此为…

[Kubernetes]1.Kubernetes(K8S)介绍,基于腾讯云的K8S环境搭建集群以及裸机搭建K8S集群

一. Kubernetes(K8S)简介 Kubernetes (K8S) 是一个为 容器化应用 提供 集群部署 和 管理 的开源工具,和docker swarm类似,由 Google 开发. Kubernetes 这个名字源于希腊语,意为 “ 舵手 ” 或 “ 飞行员 ” , k8s 这个缩写是因为 k 和 s 之间有八个字符的关系, Google…

【Jmeter】Jmeter基础8-Jmeter元件介绍之断言

断言主要用于对服务器响应的数据做验证。Jmeter提供了多个断言元件&#xff0c;其中最常用的是响应断言。 2.8.1、响应断言 作用&#xff1a;对Jmeter取样器返回值进行断言。参数说明&#xff1a; 测试字段 响应文本&#xff1a;从服务器返回的响应文本&#xff0c;Response B…