《Effective C++》《资源管理——13、以对象管理资源》

文章目录

  • 1、Terms13:Use objects to manage resources
    • 1.1、普通指针进行资源管理存在的问题
    • 1.2、RALL
    • 1.3、没有针对“动态分配数组”而设计的智能指针
  • 2、面试相关
    • 2.1 解释RAII原则,并给出它在资源管理中的重要性。举出一个可以编译的例子
    • 2.2 描述一个你曾经使用RAII原则解决的实际问题。
    • 2.3 描述一个可能导致资源泄漏的场景,并解释如何使用RAII原则来避免它
  • 3、总结
  • 4、参考

1、Terms13:Use objects to manage resources

所谓资源就是,一旦使用了它,将来必须还给系统,如果不这样,糟糕的事情就会发送。

1.1、普通指针进行资源管理存在的问题

例如我们使用一个用来塑模投资行为(例如股票,债券等等)的程序库,其中各式各样的投资类型继承于一个基类Investment:

class Investment { ... };

进一步假设,这个程序库通过一个工厂函数(条款7)供应我们获得某特定的Investment对象:

Investment* createInvestment(); //返回一个Investment继承体系中的动态分配对象

如果我们在某作用域内调用这个函数返回的对象,那么使用完这个对象之后要复制删除这个对象。

void f()
{Investment* pInv = createInvestment();//...delete pInv;
}

这种程序设计的缺陷主要在于我们可能无法释放获取的pInv对象:

(1)如果在delete之前有return语句导致函数执行结束,那么对象就无法释放;
(2)如果在delete之前程序抛出异常,那么也无法释放对象;
(3)如果这段代码在之后软件开发维护过程中被修改,那么后人可能无法知道要释放这个pInv对象,因为单纯的靠函数f中的delete语句来释放对象是行不通的。

1.2、RALL

那么为确保资源被释放,我们应该:把资源放进对象中,我们可依赖 C++ 的“析构函数自动调用机制”确保资源被释放。
以对象管理资源的思想:
(1)获得资源后立刻放进管理对象内:获得资源之后将其封装到类中,例如shared_ptr等智能指针。实际上“以对象管理资源”的观念常被称为“资源获得时机便是初始化时机”(Resource Acquisition Is Initialization,RAII)
(2)管理对象运用析构函数确保资源被释放:当离开作用域之后,对象可以调用析构函数自动的释放资源,而无须我们手动释放。但是如果析构函数抛出异常,可能需要自己手动处理(析构函数异常处理可以参阅条款8)
C++程序库提供了两种类,更加安全的管理自己的资源,分别是:shared_ptr和auto_ptr现在它已经被unique_ptr替代。
auto_ptr:
  auto_ptr是个智能指针,其析构函数自动对其所指对象调用delete
  如:我们修改函数f,并利用auto_ptr获得对象,在函数作用域结束之后,资源自动释放

void f()
{//调用函数获得对象std::auto_ptr<Investment> pInv(createInvestment());... 
}//函数执行完之后,auot_ptr的析构函数自动删除pInv

以对象管理资源的想法:

  • 获取资源后立刻放进管理对象。
  • 管理对象运用析构函数确保资源被释放。

auto_ptr对象的唯一性:auto_ptr只保存自己管理对象的一份副本,因此当auto_ptr被赋值或复制时,就会将自己的资源管理权转交给它人,从而是自己变为null

//获得一个对象,并管理该对象
std::auto_ptr<Investment> pInv1(createInvestment());//拷贝pInv1,此时pInv1被设为null,现在pInv2管理这个资源
std::auto_ptr<Investment> pInv2(pInv1);//赋值操作,此时pInv2变为null,现在pInv1管理这个资源
pInv1 = pInv2;

shared_ptr:
  shared_ptr也是智能指针,但是其与autp_ptr不同,其是“引用计数型智能指针”(RCSP),也就是多个shared_ptr可以同时指向一个管理对象。

1.3、没有针对“动态分配数组”而设计的智能指针

注意:auto_ptr和shared_ptr析构函数中做释放资源使用的是delete而不是delete [],因此将动态数组绑定于智能指针对象上是不行的,但是数组是可以与智能指针使用的。

//都是错误的
std::auto_ptr<std::string> aps(new std::string[10]);
std::shared_ptr<int> aps(new int[1024]);

C++没有为动态分配数组而设计的智能指针类,是因为C++已经有了vector和string这样的管理类,这些类已经够我们使用了,因此提供动态分配数组管理的类是多余的
  但是Boost库中的boost::scoped_array和boost::shared_array类是接近于为数组而设计的类,因此你们想要了解的话,可以参阅这两个类。

2、面试相关

2.1 解释RAII原则,并给出它在资源管理中的重要性。举出一个可以编译的例子

RAII(Resource Acquisition Is Initialization)原则是一种编程技术,用于在C++程序中管理资源。这个原则强调资源的获取(Acquisition)应该与对象的初始化(Initialization)紧密绑定,以确保资源能够在使用完毕后被正确地释放。简单来说,当一个对象被创建(即初始化)时,它会获取必要的资源,并在其生命周期结束时(即析构时)自动释放这些资源。

RAII原则在资源管理中的重要性主要体现在以下几个方面:

  1. 自动资源管理:通过将对象的生命周期与资源的生命周期绑定,可以确保资源在不再需要时得到自动释放,从而防止资源泄漏。

  2. 异常安全性:在复杂的程序中,异常处理是一个重要的部分。RAII确保即使在发生异常时,资源也能被正确释放,因为对象的析构函数总是会被调用。

  3. 代码简洁性:通过使用RAII,程序员无需显式地管理资源的释放,这使得代码更加简洁,更易于阅读和维护。

下面是一个简单的C++例子,展示了如何使用RAII原则管理动态分配的内存:

#include <iostream>// 自定义一个简单的动态数组类,用于演示RAII
class DynamicArray {
private:int* data;size_t size;public:// 构造函数:获取资源(分配内存)DynamicArray(size_t sz) : size(sz) {data = new int[size];std::cout << "Memory allocated for " << size << " integers." << std::endl;}// 析构函数:释放资源(释放内存)~DynamicArray() {delete[] data;std::cout << "Memory released." << std::endl;}// 禁止拷贝构造和拷贝赋值,以避免资源管理的复杂性DynamicArray(const DynamicArray&) = delete;DynamicArray& operator=(const DynamicArray&) = delete;// 一个简单的函数来设置数组中的值void setValue(size_t index, int value) {if (index < size) {data[index] = value;}}// 打印数组内容void printArray() const {for (size_t i = 0; i < size; ++i) {std::cout << data[i] << ' ';}std::cout << std::endl;}
};int main() {// 创建DynamicArray对象,自动分配内存{DynamicArray arr(5);// 使用资源for (size_t i = 0; i < 5; ++i) {arr.setValue(i, static_cast<int>(i));}arr.printArray();// 离开作用域时,DynamicArray的析构函数会被自动调用,从而释放内存}// 在此处,内存已经被自动释放std::cout << "After the block, memory has been released." << std::endl;return 0;
}

在上面的例子中,DynamicArray 类在构造时分配了一块内存来存储整数,并在析构时释放了这块内存。这就确保了无论何时DynamicArray对象离开其作用域,分配的内存都会被正确释放。这遵循了RAII原则,将资源的生命周期与DynamicArray对象的生命周期绑定在了一起。

2.2 描述一个你曾经使用RAII原则解决的实际问题。

在过去的一个项目中,我负责开发一个网络通讯模块,该模块需要与远程服务器建立连接,并进行数据的收发。在这个项目中,我遇到了一个问题:如何确保网络连接在不再需要时被正确关闭,以避免资源泄漏和其他潜在的网络问题。

为了解决这个问题,我使用了RAII原则。我创建了一个名为NetworkConnection的类,它负责管理与远程服务器的连接。在NetworkConnection的构造函数中,我建立了与远程服务器的连接,并在析构函数中关闭了该连接。这样,当NetworkConnection对象离开其作用域时,连接会自动关闭。

下面是一个简化的示例代码,展示了如何使用RAII原则管理网络连接:

#include <iostream>class NetworkConnection {
private:// 假设有一个用于表示网络连接的句柄或指针void* connection_handle;public:// 构造函数:建立网络连接NetworkConnection(const std::string& server_address) {std::cout << "Connecting to server at " << server_address << std::endl;// 假设这里进行了网络连接的建立操作connection_handle = /* 建立连接的代码 */;}// 析构函数:关闭网络连接~NetworkConnection() {std::cout << "Closing network connection..." << std::endl;// 假设这里进行了网络连接的关闭操作/* 关闭连接的代码 */;}// 其他成员函数,用于数据的收发等操作void sendData(const std::string& data) {// 发送数据的代码}std::string receiveData() {// 接收数据的代码return "received data";}
};int main() {// 使用RAII原则管理网络连接{NetworkConnection conn("127.0.0.1:8080");conn.sendData("Hello, server!");std::string response = conn.receiveData();std::cout << "Received: " << response << std::endl;// 当conn对象离开作用域时,其析构函数会被自动调用,从而关闭网络连接}// 在此处,网络连接已经被自动关闭std::cout << "Network connection has been closed." << std::endl;return 0;
}

通过使用RAII原则,我确保了网络连接在不再需要时能够被正确关闭,从而避免了资源泄漏和潜在的网络问题。这种方法使得代码更加简洁、可读,并且减少了出错的可能性。同时,它也使得资源管理的责任更加明确,提高了代码的可维护性。在实际项目中,这种基于RAII的资源管理方法被广泛应用于文件句柄、数据库连接、锁等各种资源的管理中。

2.3 描述一个可能导致资源泄漏的场景,并解释如何使用RAII原则来避免它

一个常见的可能导致资源泄漏的场景是文件操作。当我们在程序中打开文件进行读写操作时,如果忘记在操作完成后关闭文件,就会导致文件句柄一直被占用,造成资源泄漏。这种情况在复杂的程序中尤其容易发生,特别是当文件操作分散在多个函数或类中时。

为了避免这种情况,我们可以使用RAII原则来管理文件资源。具体做法是创建一个封装了文件操作的类,将文件句柄的打开和关闭与该类的构造函数和析构函数绑定。这样,当创建该类的对象时,文件会自动打开,当对象离开作用域或被销毁时,文件会自动关闭。

以下是一个简单的示例,展示了如何使用RAII原则来管理文件资源:

#include <fstream>
#include <iostream>
#include <stdexcept>class FileManager {
private:std::fstream fileStream;std::string fileName;public:// 构造函数:打开文件FileManager(const std::string& name) : fileName(name) {fileStream.open(fileName, std::fstream::in | std::fstream::out);if (!fileStream.is_open()) {throw std::runtime_error("Unable to open file: " + fileName);}std::cout << "File " << fileName << " opened." << std::endl;}// 析构函数:关闭文件~FileManager() {fileStream.close();std::cout << "File " << fileName << " closed." << std::endl;}// 其他成员函数,用于文件的读写等操作void writeToFile(const std::string& data) {fileStream << data;}std::string readFromFile() {std::string data;std::getline(fileStream, data);return data;}
};int main() {try {// 使用RAII原则管理文件资源{FileManager file("example.txt");file.writeToFile("Hello, World!");std::string content = file.readFromFile();std::cout << "Read from file: " << content << std::endl;// 当file对象离开作用域时,其析构函数会被自动调用,从而关闭文件}// 在此处,文件已经被自动关闭} catch (const std::exception& e) {std::cerr << "Exception: " << e.what() << std::endl;}return 0;
}

在这个示例中,我们创建了一个FileManager类,它在构造函数中打开文件,在析构函数中关闭文件。这样,无论何时FileManager对象离开其作用域或被销毁,文件都会被自动关闭,从而避免了资源泄漏。这种方法不仅简化了资源管理,还提高了代码的健壮性和可读性。

3、总结

天堂有路你不走,地狱无门你自来。

4、参考

4.1 《Effective C++》
4.2 Effective C++条款13:以对象管理资源(Use objects to manage resources)

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

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

相关文章

如何批量给Word文件增加前缀序号?“汇帮批量重命名”帮助你批量给word文件增加前缀序号。

批量给Word文件增加前缀序号的过程&#xff0c;对于经常处理大量文档的人来说&#xff0c;是一项既繁琐又必要的任务。首先&#xff0c;我们需要明确为什么要给Word文件增加前缀序号。在很多情况下&#xff0c;当我们需要按照一定的顺序对多个文档进行管理和归档时&#xff0c;…

Faiss原理和使用总结

Faiss是一种用于高维向量检索的库&#xff0c;特别适用于大规模数据集的相似性搜索。其核心原理和使用方法可以总结如下&#xff1a; Faiss的核心原理&#xff1a; 索引结构&#xff1a;Faiss提供了多种索引结构&#xff0c;包括基于树的索引&#xff08;如k-means、PCA、IVF等…

基于STC15系列库操作LED灯

一、准备工作 1. 基于STC15系列库的工程模板 参考&#xff1a;51单片机工程模板的建立&#xff08;基于STC15系列库&#xff09;-CSDN博客 2. Keil编译器 二、程序编写 1. 新建 led.c 和 led.h 文件并存放于 user/led 文件夹下&#xff1b; 2. 新建 user.c 和 user.h 文件并…

如何辨别:DNS污染or DNS劫持?

DNS劫持和DNS污染的情况在互联网中并不少见&#xff0c;到底是出现了DNS污染还是DNS劫持。什么是DNS污染&#xff1f;什么是DNS劫持&#xff1f;我们该如何辨别DNS污染和DNS劫持&#xff1f; DNS劫持&#xff1a; DNS 劫持是指恶意攻击者通过非法手段篡改了网络中的 DNS 服务…

理论知识:Top-K 准确率

Top-1 Accuracy: 这是最常见的准确率评估方式&#xff0c;指的是模型预测的最有可能的类别&#xff08;即概率最高的类别&#xff09;是否正是真实的类别。换句话说&#xff0c;就是模型的预测结果中排名第一的类别是否正确。 Top-3 Accuracy: 这个评估标准比 Top-1 更宽松一些…

android 创建module

文章目的&#xff1a; 快速创建module并使用 创建步骤&#xff1a; 1 创建module 2 修改module下的build.gradle文件 3 修改清单文件中MainActivity属性&#xff0c;否则APP会因为有多个启动界面而崩溃 4 在主项目build.gradle引用该object Module 至此&#xff0c;可在APP中…

Golang的[]interface{}为什么不能接收[]int?

在 Go 中&#xff0c;[]interface{} 和 []int 是两种不同的类型&#xff0c;虽然它们的底层数据结构都是切片&#xff0c;但是它们的元素类型不同。[]interface{} 是一个空接口切片&#xff0c;可以容纳任意类型的元素&#xff0c;而 []int 是一个整数切片&#xff0c;只能容纳…

vue项目生成二维码(并避免重复生成)

一、需求&#xff0c;按连接&#xff08;带参数&#xff09;生成二维码 二、步骤 1、先安装插件 npm install qrcodejs2 --save-dev2、在需要用到二维码的页面引入 import QRCode from qrcodejs2;3、主要代码 <div id"qrCode2" ref"qrCodeDiv" class&…

针对springcloud gateway 跨域问题解决方案

springcloud gateway版本 <spring-boot.version>2.3.3.RELEASE</spring-boot.version> <spring-cloud.version>Hoxton.SR8</spring-cloud.version>跨域问题说明 application:1 Access to XMLHttpRequest at https://xxxxxxxxxx from origin http://l…

原生JS如何实现验证码

在原生JavaScript中实现验证码通常涉及到创建一些随机字符或图像&#xff0c;然后让用户输入这些字符以验证他们的身份。以下是一个简单的示例&#xff0c;说明如何使用原生JavaScript创建一个基于文本的验证码。 创建验证码字符串&#xff1a;首先&#xff0c;我们需要一个函…

Goland远程连接Linux进行项目开发

文章目录 1、Linux上安装go的环境&#xff12;、配置远程连接3、其他配置入口 跑新项目&#xff0c;有个confluent-Kafka-go的依赖在Windows上编译不通过&#xff0c;报错信息&#xff1a; undefined reference to __imp__xxx似乎是这个依赖在Windows上不支持&#xff0c;选择让…

IMX6ULL-UBOOT驱动移植

介绍 IMX6ULL正点原子开发板使用的是14x14_evk的芯片 其中14x14代表的是芯片的尺寸。 本教程的标识符以nsouther或者 NSOUTHER NSouther为主 添加板子自己的配置文件 板子的默认配置文件保存在 configs目录下&#xff0c;我们以mx6ull_14x14_evk_emmc_defconfig为主&#xf…

SOT23-6封装单键触摸感应触发芯片TC233A

前言&#xff1a; 触摸芯片很多&#xff0c;现在触摸按键已经应用到很多行业&#xff0c;虽然不能覆盖所有的按键&#xff0c;但确实用的越来越多&#xff0c;国产的价格也便宜的令人发指&#xff0c;比如这个TC233A&#xff0c;也就一毛多一点。 TC233A概述 TC233A 是一个单…

vue 钩子函数

目录 钩子函数概念 生命周期钩子函数 keep-alive 钩子函数 自定义指令的钩子函数 路由导航 / 路由守卫 钩子函数 全局守卫 路由独享守卫 导航守卫 钩子函数概念 在 vue 中可以自动执行的函数叫做钩子函数 生命周期钩子函数 vue 从实例创建到销毁过程中被自动执行的函…

Logback:新版本报no applicable action for [Encoding]问题

logback.xml配置文件如下 <?xml version"1.0" encoding"UTF-8"?><configuration><include resource"org/springframework/boot/logging/logback/base.xml"/><!-- 日志输出的通道 --><appender name"STDOUT&qu…

mac tcp实现客户端与服务端进行图像传输及处理

客户端发送图像到服务端&#xff0c;服务端对图像进行处理&#xff0c;在将处理后的图像发送到客户端&#xff0c;并且服务端持续监听客户端。 客户端 #include <iostream> #include <fstream> #include <vector> #include <unistd.h> #include <…

【Shell语言】linux中awk命令

linux中awk命令 看这里放声嘶吼谁也不舍得沉默 宽阔也抓不住我下一秒钟的echo ——《暂时失控》苏打绿 awk命令简介 AWK 是一种处理文本文件的语言&#xff0c;是一个强大的文本分析工具。 之所以叫 AWK 是因为其取了三位创始人 Alfred Aho&#xff0c;Peter Weinberger, 和 B…

请陪伴Kimi和GPT成长

经验的闪光汤圆 但是我想要写实的 你有吗&#xff1f; 岁数大了&#xff0c;希望如何学习新知识呢&#xff1f;又觉得自己哪些能力亟需补强呢&#xff1f; 看论文自然得用Kimi&#xff0c;主要是肝不动了&#xff0c;眼睛也顶不住了。 正好昨天跟专业人士学会了用工作流的办法跟…

【Java】ArrayList removeIf() 方法

removeIf() 方法用于删除所有满足特定条件的数组元素。 removeIf()方法的语法为&#xff1a; arraylist.removeIf(Predicate<E> filter) 注&#xff1a;arraylist 是 ArrayList 类的一个对象。 参数说明&#xff1a;filter - 过滤器&#xff0c;判断元素是否要删除 返回…

Python中的Super方法实现问题及解决方案

1、问题背景 在Python中&#xff0c;super方法用于在子类中调用父类的方法。Guido van Rossum曾给出了一个纯Python实现的super方法&#xff0c;以便更好地理解其工作原理。然而&#xff0c;在这个实现中&#xff0c;存在一个问题&#xff1a;当传入的对象不是要调用的父类的实…