当前位置: 首页 > news >正文

Effective C++ 改善程序与设计的55个具体做法笔记与心得 3

三. 资源管理

13. 以对象管理资源

请记住

  • 为防止资源泄露,使用智能指针

14. 在资源管理类中小心copying行为

请记住

  • 复制RAII对象必须一并复制他所管理的资源,所以资源的copying行为决定RAII对象的copying行为
  • 普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法(reference counting)。不过其他行为也都可能被实现。

解释
‌‌‌‌  RAII(资源获取即初始化)是C++中一个非常重要的概念,它用于管理对象生命周期内的资源分配和释放问题。

‌‌‌‌  对于复制RAII对象,首要问题就是如何处理它所管理的资源。实际上,资源的复制行为决定了RAII对象的复制行为,不同的策略会对管理资源的对象的复制产生不同的影响:

  • 抑制复制(Prohibit Copying):既简单又有效。在这种情况下,复制构造函数和赋值运算符通常被声明为private,并且不传递任何实现。这阻止了复制和赋值。这是一个好的默认行为,通常适用于管理不可共享资源的类(如文件、线程、互斥锁等)。
class Uncopyable {
private:Uncopyable(const Uncopyable&);Uncopyable& operator=(const Uncopyable&);
};
  • 引用计数(Reference Counting):这种方法允许复制,同时确保原始资源的生命周期恰到好处。具体来说,只有当最后一个引用被销毁时,才会清理底层资源。std::shared_ptr是一种典型的实现引用计数的RAII类。
std::shared_ptr<int> p(new int[10]);
std::shared_ptr<int> q = p;  // 引用计数增加
  • 深度复制(Deep Copying):在这种策略中,我们为每个RAII对象创建一份新的资源副本。这比引用计数更安全,但可能更为费时,因为它需要复制所有数据。

  • 转移资源所有权(Transfer Ownership):通过移动语义(C++11开始提供),可以将资源从一个对象转移到另一个对象,而不需要复制。这通常适用于管理大块资源的RAII对象,例如std::unique_ptr

根据特定的类管理的资源类型和预期的用途,可以选择适合的策略来处理RAII对象的复制行为。这是面向对象的设计的一个重要方面。

15. 在资源管理类中提供对原始资源的访问

请记住

  • APIs往往要求访问原始资源,所以每一个RAI class应该提供一个“取得其所管理之资源”的办法。
  • 对原始资源的访问可能经由显式转换或隐式转换。一般而言显示转换比较安全,但隐式转换对客户比较方便。

解释
‌‌‌‌  资源获取即初始化(RAII)的类确实经常需要提供获取其管理的资源的方式。这样的设计并不会违背RAII原则,因为资源的所有权仍然在RAII对象中,但一些情况下确实需要让程序员能够访问这些底层资源。

这些底层资源的访问方式主要分为两种:

  • 显式转换:这样的设计使得访问者必须显示地请求访问资源。这样可以减少因误用底层资源而导致的问题。std::unique_ptr就是一个很好的例子,它提供了.get()方法来显式获取底层的原始指针。
std::unique_ptr<int> p(new int());
int* raw = p.get();
  • 隐式转换:这提供了对使用者更为简单的访问方式,但是如果使用不当,可能带来风险。比如,std::shared_ptr可以被隐式转化为bool类型,这使得我们能够在条件语句中使用。
std::shared_ptr<int> p(new int());
if (p) {// 如果 p 管理着一个对象,那么将会执行这里的代码
}

‌‌‌‌  一般来说,显式转换更为安全,因为它强制要求使用者意识到他们正在直接操作底层资源。然而,隐式转换可以使代码更简洁,提供更好的用户体验。因此,应根据特定的使用场景和对安全性的要求来确定哪种方法更为合适。

16. 成对使用new和delete时要采取相同形式

请记住

  • 如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。
  • 如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。

解释
‌‌‌‌  在C++中,这个规则非常重要,因为不正确地使用new[]delete[]会导致未定义的行为。

  1. 当我们使用new[]来分配数组时,必须使用delete[]来删除该数组。如果我们只用delete,就可能会出现内存泄漏。
int* array = new int[10];
delete[] array; // 正确的使用方式
  1. 另一方面,当我们使用new分配单个元素时,必须使用delete(不带[])来删除。如果我们使用delete[]来删除非数组类型的指针,那也会导致未定义的行为。
int* single = new int();
delete single; // 正确的使用方式

‌‌‌‌  这组规则是相当重要的,因为它们涉及到C++运行时环境如何在堆上分配和管理内存。遵循这些规则不仅可以避免内存泄漏,也能防止程序因为错误的内存释放操作而崩溃。

newnew[]以及deletedelete[]的区别主要在于它们处理的数据结构和内存分配的方式:

  1. newdelete:这两个操作符主要用于分配和释放单个对象的内存。
int* p = new int;       // 分配一个整数的内存
delete p;               // 释放该整数的内存
  1. new[]delete[]:这两个操作符用于分配和释放数组的内存。
int* arr = new int[10]; // 分配10个整数的内存
delete[] arr;           // 释放这10个整数的内存

‌‌‌‌  对于这两对操作符,最重要的一点是必须配对使用。也就是说,我们不能使用new来分配内存然后使用delete[]释放内存,反之亦然。

‌‌‌‌  注意:newnew[]分配的内存不会被自动释放,我们需要手动调用相应的delete或者delete[]来释放这些内存,否则将导致内存泄漏。这是一个常见的编码错误,也是使用RAII(资源获取即初始化)技术的主要原因,它可以通过在对象的生命周期结束时自动释放其拥有的资源,从而防止内存泄漏。

17. 以独立语句将newed对象置入智能指针

请记住
‌‌‌‌  以独立语句将newed对象存储于智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。

解释
‌‌‌‌  智能指针主要的优点就是它可以保证在任何情况下,包括出现异常时,都可以正确地释放内存。在C++中,如果我们创建了一个普通的指针并使用new分配了内存,但在释放这个内存前出现了异常,那么这个内存就会泄露。

‌‌‌‌  由于异常可能在new之后的任何时间发生,所以我们无法确定何时捕获并处理这个异常。因此,为了避免内存泄露,我们应该尽快将这个指针转换为智能指针。如果我们在创建智能指针对象的同一条语句中使用new,那么即使出现异常,内存也会安全地释放。

std::unique_ptr<int> ptr(new int(10));    // 创建一个存储int的智能指针

‌‌‌‌  虽然将new和智能指针的初始化放在同一行更简洁,但为了避免new在智能指针接管之前就抛出异常导致的任何可能的资源泄露,提倡以独立语句将newed对象存储于智能指针内。如:

int* temp = new int(10);
std::unique_ptr<int> ptr(temp);    // 创建一个存储int的智能指针

‌‌‌‌  这样,即使在new和智能指针的初始化之间抛出异常,我们也可以保证delete temp;被调用,防止了内存泄漏。

相关文章:

  • SonarQube集成Jenkins平台搭建
  • 【Python】一文向您详细解析内置装饰器 @lru_cache
  • 【Android面试八股文】Kotlin内置标准函数let的原理是什么?
  • 初识C++ · 继承(1)
  • 乐鑫ESP32相关资料整理
  • 喜马拉雅项目调整
  • 让NSdata对象转变成UIImage对象再裁剪图片的方法
  • Linux--视频推流及问题
  • 新渠道+1!TDengine Cloud 入驻 Azure Marketplace
  • 代码随想录刷题复习day01
  • Java多线程设计模式之保护性暂挂模式
  • 关于Threejs的使用二
  • 东芝-Soft Limit 报警及其解决办法
  • 代码随想录算法训练营第29天(贪心)|455.分发饼干、376. 摆动序列、53. 最大子序和
  • C语言 图的基础知识
  • 【剑指offer】让抽象问题具体化
  • Apache Pulsar 2.1 重磅发布
  • ESLint简单操作
  • happypack两次报错的问题
  • iOS编译提示和导航提示
  • JavaScript对象详解
  • Java新版本的开发已正式进入轨道,版本号18.3
  • leetcode386. Lexicographical Numbers
  • select2 取值 遍历 设置默认值
  • 安装python包到指定虚拟环境
  • 代理模式
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 实现简单的正则表达式引擎
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • 王永庆:技术创新改变教育未来
  • 网页视频流m3u8/ts视频下载
  • 线上 python http server profile 实践
  • 一道闭包题引发的思考
  • 用element的upload组件实现多图片上传和压缩
  • 正则与JS中的正则
  • 摩拜创始人胡玮炜也彻底离开了,共享单车行业还有未来吗? ...
  • ​Spring Boot 分片上传文件
  • #DBA杂记1
  • #define
  • $forceUpdate()函数
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (C++)八皇后问题
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (k8s)kubernetes 部署Promehteus学习之路
  • (八)Flask之app.route装饰器函数的参数
  • (附源码)spring boot基于小程序酒店疫情系统 毕业设计 091931
  • (附源码)springboot码头作业管理系统 毕业设计 341654
  • (三)终结任务
  • (十八)Flink CEP 详解
  • (四)opengl函数加载和错误处理
  • (一)十分简易快速 自己训练样本 opencv级联haar分类器 车牌识别
  • (转)EXC_BREAKPOINT僵尸错误
  • (转)母版页和相对路径
  • *算法训练(leetcode)第四十五天 | 101. 孤岛的总面积、102. 沉没孤岛、103. 水流问题、104. 建造最大岛屿