C++:什么是RAII?

2023-11-03

一、什么是RAII?

RAll (Resource Acquisition ls Initialization)是由c++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化,使用局部对象来管理资源的技术称为资源获取即初始化;这里的资源主要是指操作系统中有限的东西如内存(heap)、网络套接字,互斥量,文件句柄等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入。

\RAlI的原理

资源的使用一般经历三个步骤:

a.获取资源(创建对象)

b.使用资源

c.销毁资源(析构对象)

但是资源的销毁往往是程序员经常忘记的一个环节,所以程序界就想如何在程序员中让资源自动销毁呢?

解决问题的方案是:RAll,它充分的利用了C++语言局部对象自动销毁的特性来控制资源的生命周期。给一个简单的例子来看下局部对象的自动销毁的特性:

class Object{
private:
int *ip
public:
Object(int val=0){
ip=new int(val);
}
~Object(){
delete ip;
}
};
void fun ()
{
Object obj;
return;
}

从该示例可以看出,当我们在fun函数中声明一个局部对象的时候,会自动调用构造函数进行对象的初始化,当整个fun函数执行完成之后,自动调用析构函数来销毁对象,整个过程无需人工介入,由操作系统自动完成,于是自然联想到,当我们在使用资源的时候,在构造函数中进行初始化,在析构函数中进行销毁。

整个RAII过程总结为四个步骤:

a:设计一个类封装资源

b:在构造函数中初始化

c:在析构函数中进行销毁操作

d:使用时定义一个该类的对象

二、智能指针的引入

智能指针是比原始指针更智能的类,解决悬空(dangling)指针或多次删除被指向对象,以及资源泄露问题,通常用来确保指针的寿命和其指向对象的寿命一致。智能指针虽然很智能,但容易被误用,智能也是有代价的。\n\n

1.为什么要使用智能指针\n因为裸指针存在很多问题,主要是下面这些:

1.难以区分指向的是单个对象还是一个数组;

2.使用完指针之后无法判断是否应该销毁指针,因为无法判断指针是否“拥有”指向的对象;

3.在已经确定需要销毁指针的情况下,也无法确定是用delete关键字删除,还是有其他特殊的销毁机制,例如通过将指针传入某个特定的销毁函数来销毁指针;

4.即便已经确定了销毁指针的方法,由于1的原因,仍然无法确定到底是用delete(销毁单个对象)还是delete[] (销毁一个数组);

5.假设上述的问题都解决了,也很难保证在代码的所有路径中(分支结构,异常导致的跳转),有且仅有一次销毁指针操作;任何一条路径遗漏都可能导致内存泄露,而销毁多次则会导致未定义行为

6.理论上没有方法来分辨一个指针是否处于悬挂状态。

三、auto_ptr

C++11提供了四种智能指针:auto_ptr; unique_ptr; shared_ptr;  wek_ptr;

四、模拟实现auto_ptr

对于auto_ptr类,它的类成员除了一个指针,还有一个拥有权成员,它的作用时记录指针是否对所指之物有拥有权。

template<typename _Ty>
class my_auto_ptr{
private:
	bool _Owns;
	_Ty* _ptr;
public:
	my_auto_ptr(_Ty* p = nullptr) :_Owns(p != nullptr), _ptr(p) 
	{

	}
	~my_auto_ptr() {
		if (_Owns) {
			delete _ptr;
		}
		_Owns = false;
		_ptr = nullptr;
	}

};

使用一个类来测试一下

class Object
{
private:
	int num;
public:
	Object(int x = 0) : num(x)


	{
		cout << "Create Object: " << this << endl;
	}
	~Object()
	{
		cout << "Destroy Object: " << this << endl;
	}
};

int main(void)
{
	my_auto_ptr<Object> obj(new Object(10));
	return 0;

}

运行结果

构造

 析构

图示如下

 

1.(*)和(->)重载

为了方便调用智能指针指向对象的方法,可以对其进行重载:

比如Object对象里有其他方法

int& value(){
     return num;
}
const int & value() const{
     return num;
}

 运算符重载:

对于解引用的重载,利用get()得到_Ptr;对其解引用得到_Ptr指向的对象,以引用返回。

对于指向符的重载,直接返回get()即可。

_Ty* get()const{
return _Ptr;
}
_Ty& operator*()const
{
    return *(get());
}
_Ty* operator->()const
{
    return get();
}

使用示例:

int maina()
{
my_auto_ptr<Object> obj(new Object(10));
    cout<<obj->value()<<endl;
    cout<<(*obj).value()<<endl;

     return 0;
}

2.重置指向(reset)和释放函数(release)

重置智能指针指向,意思就是不指向当前对象,指向另外一个对象。

void reset(_Ty* p=nullptr)
{
    if(_Owns)
    {
       delete _Ptr;
    }
    _Ptr=p;
}

释放即失去当前对象的拥有权,可以利用返回值方式让其他指针指向当前对象。

_Ty* release() const
{
	_Ty* tmp = nullptr;
	if (_Owns)
	{
		((my_auto_ptr*)this)->_Owns = false;
		tmp = _Ptr;
		((my_auto_ptr*)this)->_Ptr = nullptr;
	}
	return tmp;
}

3.拷贝构造带来的问题

一:重复析构即两个指针指向同一块内存

如果拷贝构造是浅拷贝就会出现下面这种情况

my_auto_ptr(const my_auto_ptr& op)
	: _Owns(op._Owns)
{
	if (_Owns) {
		_Ptr = op._Ptr;
	}
}
int main(void)
{
	my_auto_ptr<Object> obja(new Object(10));
	
	my_auto_ptr<Object> objb(obja);

	return 0;
}

运行结构程序结束时,会对指向Object对象析构两次,导致堆破坏。

二.失去拥有权引发程序崩溃

在拷贝构造中,让拷贝的智能指针释放资源转移给待拷贝的智能指针

my_auto_ptr(const my_auto_ptr& op)
		: _Owns(op._Owns), _Ptr(op.release())
	{
	}

调用fun()函数时将object作为参数传递过去:

void fun(my_auto_ptr<Object> op)
{
	cout << op->value() << endl;
}

int main(void)
{
	my_auto_ptr<Object> obja(new Object(10));
	
	fun(obja);
	cout << obja->value() << endl;
	return 0;
}

传递给形参时,需要调用拷贝构造函数,那么就会将资源转移给形参op,在fun()函数结束时形参op会自动析构掉。

如图:

在进入fun()函数时,对象拥有权已经在形参手里,原来的obja指针已经不拥有Object对象了

在fun()函数结束时,形参op也自动析构,也析构了Object对象。

那么再想通过原来的obja指针来访问Object对象,就会引发异常:读取访问权限冲突。

赋值运算符重载和拷贝构造一样,会引发这些问题。 

4.指向一组对象引发的错误

于在auto_ptr的析构函数设计的是 释放当前指针指向的内容,即delete _Ptr; 这样在初始化指向一组对象时,析构函数只析构了一个对象,引发内存泄漏。

总结:

auto_ptr主要有三大问题:

1.复制和赋值会改变资源的所有权

2.在STL容器中使用auto_ptr有极大风险,因为容器中元素必须支持可复制和可赋值。

3.不支持对象数组操作

C++11中auto_ptr.
 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++:什么是RAII? 的相关文章

  • InvalidOperationException - 对象当前正在其他地方使用 - 红十字

    我有一个 C 桌面应用程序 其中我连续创建的一个线程从源 实际上是一台数码相机 获取图像并将其放在 GUI 中的面板 panel Image img 上 这必须是另一个线程 如它是控件的代码隐藏 该应用程序可以工作 但在某些机器上 我会在随
  • 每个托管线程是否都有自己对应的本机线程?

    我想知道是否在 Net 中创建托管线程 通过调用Thread Start 导致在后台创建一个本机线程 那么托管线程是否有对应的本机线程呢 如果是 当托管线程等待或睡眠时 是否意味着相应的本机线程也在等待或睡眠 是的 NET 线程映射到所有当
  • 确保 StreamReader 不会挂起等待数据

    下面的代码读取从 tcp 客户端流读取的所有内容 并且在下一次迭代中它将仅位于 Read 上 我假设正在等待数据 我如何确保它不会在没有任何内容可供读取时返回 我是否必须设置低超时 并在失败时响应异常 或者有更好的办法吗 TcpClient
  • 复制 std::function 的成本有多高?

    While std function是可移动的 但在某些情况下不可能或不方便 复制它会受到重大处罚吗 它是否可能取决于捕获变量的大小 如果它是使用 lambda 表达式创建的 它依赖于实现吗 std function通常被实现为值语义 小缓
  • 在 C 中匹配二进制模式

    我目前正在开发一个 C 程序 需要解析一些定制的数据结构 幸运的是我知道它们是如何构造的 但是我不确定如何在 C 中实现我的解析器 每个结构的长度都是 32 位 并且每个结构都可以通过其二进制签名来识别 举个例子 有两个我感兴趣的特定结构
  • 复制目录内容

    我想将目录 tmp1 的内容复制到另一个目录 tmp2 tmp1 可能包含文件和其他目录 我想使用C C 复制tmp1的内容 包括模式 如果 tmp1 包含目录树 我想递归复制它们 最简单的解决方案是什么 我找到了一个解决方案来打开目录并读
  • 如何区分用户点击链接和页面自动重定向?

    拥有 C WebBrowser control http msdn microsoft com en us library system windows forms webbrowser aspx在我的 WinForms 应用程序中 并意识
  • 获取两个工作日之间的天数差异

    这听起来很简单 但我不明白其中的意义 那么获取两次之间的天数的最简单方法是什么DayOfWeeks当第一个是起点时 如果下一个工作日较早 则应考虑在下周 The DayOfWeek 枚举 http 20 20 5B1 5D 3a 20htt
  • 如何使用 LINQ2SQL 连接两个不同上下文的表?

    我的应用程序中有 2 个数据上下文 不同的数据库 并且需要能够通过上下文 B 中的表的右连接来查询上下文 A 中的表 我该如何在 LINQ2SQL 中执行此操作 Why 我们正在使用 SaaS 产品来跟踪我们的时间 项目等 并希望向该产品发
  • Qt - ubuntu中的串口名称

    我在 Ubuntu 上查找串行端口名称时遇到问题 如您所知 为了在 Windows 上读取串口 我们可以使用以下代码 serial gt setPortName com3 但是当我在 Ubuntu 上编译这段代码时 我无法使用这段代码 se
  • 如何在 Xaml 文本中添加电子邮件链接?

    我在 Windows Phone 8 应用程序中有一些大文本 我希望其中有电子邮件链接 例如 mailto 功能 这是代码的一部分
  • 为什么 std::strstream 被弃用?

    我最近发现std strstream已被弃用 取而代之的是std stringstream 我已经有一段时间没有使用它了 但它做了我当时需要做的事情 所以很惊讶听到它的弃用 我的问题是为什么做出这个决定 有什么好处std stringstr
  • CMake 无法确定目标的链接器语言

    首先 我查看了this https stackoverflow com questions 11801186 cmake unable to determine linker language with c发帖并找不到解决我的问题的方法 我
  • AES 128 CBC 蒙特卡罗测试

    我正在 AES 128 CBC 上执行 MCT 如中所述http csrc nist gov groups STM cavp documents aes AESAVS pdf http csrc nist gov groups STM ca
  • 使用管道时,如果子进程数量大于处理器数量,进程是否会被阻塞?

    当子进程数量很大时 我的程序停止运行 我不知道问题是什么 但我猜子进程在运行时以某种方式被阻止 下面是该程序的主要工作流程 void function int process num int i initial variables for
  • 动态添加 ASP.Net 控件

    我有一个存储过程 它根据数据库中存储的记录数返回多行 现在我想有一种方法来创建 div 带有包含该行值的控件的标记 如果从数据库返回 10 行 则 10 div 必须创建标签 我有下面的代码来从数据库中获取结果 但我不知道如何从这里继续 S
  • 使用 C# 读取 Soap 消息

  • 无法接收 UDP Windows RT

    我正在为 Windows 8 RT 编写一个 Windows Store Metro Modern RT 应用程序 需要在端口 49030 上接收 UDP 数据包 但我似乎无法接收任何数据包 我已按照使用教程进行操作DatagramSock
  • 我的班级应该订阅自己的公共活动吗?

    我正在使用 C 3 0 遵循标准事件模式我有 public event EventHandler
  • 使用 .NET Process.Start 运行时挂起进程 - 出了什么问题?

    我在 svn exe 周围编写了一个快速而肮脏的包装器来检索一些内容并对其执行某些操作 但对于某些输入 它偶尔会重复挂起并且无法完成 例如 一个调用是 svn list svn list http myserver 84 svn Docum

随机推荐

  • C语言题目代码总结解析

    目录 简单版三子棋实现 简单的扫雷的实现 简单的通讯录实现 最大公约数 辗转相除法 判断一个数是否是素数 二分查找 有序数组查找 递归实现字符串反转 递归实现汉诺塔问题 青蛙跳台阶问题 几个字符串库函数的实现 qsort的冒泡实现版本 杨式
  • 用代码写出浪漫__合集(python、matplotlib、Matlab、java绘制爱心、玫瑰花、前端特效玫瑰、爱心)

    活动地址 CSDN21天学习挑战赛 用代码写出浪漫合集 爱心 玫瑰花 本文目录 一 前言 二 用python matplotlib Matlab java绘制爱心 1 爱心图形1 弧线型 显示的文字写在代码里 2 爱心图形2 直线型 显示的
  • openeuler 欧拉操作系统的几个图形界面安装方法

    欧拉操作系统openeuler 安装的时候默认是不带图形界面的 安装完成后如果要使用图形需要手工往系统里面补 目前为止最新的21 09版本ISO安装完后在线源配置里面EPOL源路径是错误的 需要手工修改一下路径 否则是无法更新源里面的软件包
  • let和const 和var 的区别

    1 let和const是什么 声明变量或声明常量l var声明变量 let 代替var 声明变量 const声明常量constant 2 let和const的用法 var一样 var username Alex let age 18 con
  • 图解LeetCode14:最长公共前缀(递归,二分查找)

    LeetCode14 最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀 如果不存在公共前缀 返回空字符串 示例 输入 strs flowers flow flight 输出 fl 输入 strs dog racecar car 输
  • Linux的shell入门和版本控制(五)

    0 前言 这部分简单介绍了Linux系统中的shell编程 1 服务监听 在Linux中的服务监听 相当于在windows中的任务管理器 常用指令 示例一 查询进程 ps aux grep 要查询的程序名 这样查询会连带这条查询指令的进程一
  • 嵌入式Linux开发: 从0开始编译并启动ARM Linux内核(全志)

    引言 最近看见很多小白不会编译Linux内核 自己瞎折腾走了很多弯路 本文章将会以Orange Pi 香橙派 Zero开发板为例 带您成功编译内核并在板子上启动它 准备 您需要一台Ubuntu PC 版本最好在20 04以上 一个可以用的U
  • TCGA数据库详解

    TCGA The cancer genome atlas 癌症基因组图谱 由 National Cancer Institute NCI 美国国家癌症研究所 和 National Human Genome Research Institut
  • C#位运算示例

    在C 中可以对整型运算对象按位进行逻辑运算 按位进行逻辑运算的意义是 依次取被运算对象的每个位 进行逻辑运算 每个位的逻辑运算结果是结果值的每个位 C 支持的位逻辑运算符如表2 9所示 运算符号 意义 运算对象类型 运算结果类型 对象数 实
  • 即将成为史上最具用户体验的Hexo+GitHub Pages搭建博客的教程(持续更新中)

    前言 网上关于Hexo Github Pages搭建博客的教程很多 但是参阅很多博文 都是表达不够清晰 绕来绕去 基于此 我想以一个初来者的角度写一篇尽可能靠谱的教程 方便大家快速搭建好 大致流程 搭建Node js环境 搭建Git环境 搭
  • c++ 习题(1)

    1 输入一个正整数n 计算下式的和求e的值 保留4位小数 e 1 输入输出示例 Input n 10 e 2 7183 if 1 include stdio h double facabular int n int sum 1 i for
  • 如何在C语言循环里实现多线程,如何用C语言实现多线程

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 Windows操作系统 C语言实现多线程 include include DWORD APIENTRY ThreadOne LPVOID threadArg printf 线程开始啦 参数是 s
  • 买车注意事项

    YETI 2016创行 车 保险 税 上牌 17 2w 其中裸车15 76w GS4 手动豪华 我的手动豪华117500包上牌 交强险 购置税 全车膜 脚垫 侧蹋 行车记录仪 发动机护板 挡泥板 座套 方向盘套 前两次首保免费 10万公里机
  • Git背后的设计理念

    首先要清楚Git版本管理中提交的概念 通过按行对比 line diff 将有差异的部分作为增量补丁 使用git add添加到暂存区里的每一个文件都会由按行对比得到他们的增量补丁 而使用git commit将暂存区里的所有文件的增量补丁合并起
  • 泛型知识点总结

    目录 泛型的概念 泛型的底层实现 什么是类型擦除 Java编译器具体是如何擦除泛型的 对于泛型的理解 泛型的使用注意点 零散知识点1 泛型的使用注意点 零散知识点2 泛型方法 类型变量的限定 泛型的概念 泛型的底层实现 我们知道 Java有
  • Java项目结构的总体理解

    本文简单记录了一下自己对java项目各个层的理解 清理笔记 hhh 新版编辑器既好用又不好用 哎 第一次发布的序号都被打乱了 参考 作者 码农BookSea 原文链接 DAO层和Service层的究极理解 这波我在大气层 码农BookSea
  • doGet与doPost的区别

    在使用表单提交数据到服务器的时候有两张方式可共选择 一个是post一个是get 可在
  • 公有云和私有云的区别

    在现在云计算大行其道的时候 许多的企业都将自己的数据信息进行云迁移 但是面对种类繁多的云服务 企业应该如何选择适合自己的业务呢 首先我们要了解什么是云计算 云计算有几种模式 各种模式的架构原理 什么是云计算云计算将计算作为一种服务交付给用户
  • 浪潮服务器硬盘阵列怎么做,server - 浪潮服务器RAID阵列配置及OS安装

    1 RAID磁盘阵列配置 1 1RAID含义 磁盘阵列 Redundant Arrays of Independent Disks RAID 有 独立磁盘构成的具有冗余能力的阵列 之意 1 2RAID配置作用 将独立磁盘 组合成容量巨大的磁
  • C++:什么是RAII?

    一 什么是RAII RAll Resource Acquisition ls Initialization 是由c 之父Bjarne Stroustrup提出的 中文翻译为资源获取即初始化 使用局部对象来管理资源的技术称为资源获取即初始化