Effective C++ 3nd——-资源管理

以对象管理资源

简单来说就是要用类来管理资源,最好使用C++11新标准提供的几种智能指针
请记住:

  • 为防止资源泄漏,请使用RAII对象,他们在构造函数中获得资源并在析构函数中释放资源
  • 两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,因为拷贝行为比较直观。若选择auto_ptr,拷贝动作会使被拷贝的对象指向NULL

RAII:RAII即 “资源获取即初始化”,它是一种方法,定义一个类来封装资源的分配和释放,在类的构造函数中来实现资源的分配和初始化,在类的析构函数中实现资源的释放和清理,是C++中一种管理资源,避免资源泄漏的方法,因为在C++中任何类构造的对象最终都会调用析构函数销毁

在资源管理类中小心 copyingcopyingcopying 行为

在上述中讲述了基于堆分配的资源管理,然而并非所有资源都是基于堆的,对那种资源而言,像auto_ptr和shared_ptr这样的智能指针往往不适合作为资源掌管着。既然如此,有可能你需要建立自己的资源管理类

假设使用C API函数处理类型为Mutex的互斥器对象,共有lock和unlock两函数可用:

void lock(Mutex &m);
void unlock(Mutex &m);

为确保绝不会忘记将一个被锁住的Mutex解锁,你可能会希望建立一个类用来管理锁。

class Lock{public:// 获得资源explicit Lock(Mutex &pm) : mutexPtr(pm){ lock(mutexPtr); }// 释放资源~Lock(){ unlock(mutexPtr); }
private:Mutex *mutexPtr;
};// 客户对Lock的用法符合RAII方式
Mutex m;
{ Lock m1(&m); }  // 在区块最末尾,自动解锁

这很好,但是如果Lock对象被复制,会发生什么事

Lock m11(&m);
Lock m12(m11);

这是一个一般化问题的特定例子,大多数时候会选择以下两种可能:

  • 禁止复制:许多时候允许RAII对象被复制本身不合理,此时可以将cpoying操作声明为私有的
  • 使用引用计数法:类似shared_ptr的做法
    • 但是shared_ptr指针默认当资源的引用计数为零时删除所指物,并不是删除。但shared_ptr允许指定“删除器”,当引用计数器为零时便被调用,删除器对shared_ptr构造函数而言是第二参数,可有可无。当指定删除器时:
    class Lock{public:// 以某个Mutex初始化shared_ptr,并以unlock函数为删除器explicit Lock(Mutex &pm) : mutexPtr(pm, unlock){ lock(mutexPtr.get()); }
    private:std::tr1::shared_ptr<Mutex> mutexPtr;
    
  • 复制底部资源:有时候,只要你喜欢,可以针对一份资源拥有其任意数量的复件。而你需要“资源管理类”的唯一理由是,当你不再需要某个复件时确保它被释放。在此情况下复制资源管理对象,应该同时也复制其所指的资源。也就是说,复制资源管理对象时,进行的是深拷贝
  • 转移底部资源的拥有权:在某些场合下你希望确保永远只有一个RAII对象指向一个未加工资源,即使RAII对象被复制依然如此。类似auto_ptr,即资源的所有权会从被复制对象转移到目标对象

请记住:

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

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

简单来说,就是在自己设计的资源管理类中要提供对其所指或所含资源的访问方法,而不是直接处理原始资源。这样你对资源的所有操作就不会离开设计的资源管理类,也会更加安全。

考虑以下代码:

class Investment { };
std::tr1::shared_ptr<Investment> pInv(createInvestment());// 假设你希望以某个函数处理Investment对象
int daysHeld(const Investment* pi);  // 返回投资天数int days = daysHeld(pInv);  // 这样使用会报错,因为函数的形参与实参不匹配

解决方法有两种,分别是显示类型转换隐式类型转换

  • 显示类型转换可以在类里面定义一个访问资源的成员函数,例如 shared_ptr 中的 get 成员函数

    int days = daysHeld(pInv.get());  // 这样就不会报错
    
  • shared_ptr 和 auto_ptr 都重载了 operator -> 运算符和 operator* 运算符,它们允许隐式转换至底部原始指针

由于有时候还是必须取得RAII对象内的原始资源,于是我们可以在类里面提供一个隐式转换函数。考虑以下代码:

FontHandle getFont();  // 这是个C API,用来获取字体
void releaseFont(FontHandle fh);  // 释放字体class Font{public:explicit Font(FontHandle fh) : f(fh) { }~Font() { release(f); }
private:FontHandle f;

假设有大量与字体相关的C API,它们处理的是FontHandle,那么“将Font对象转换为FontHandle”会是一种很频繁的需求。除了可以在 Font 类里面定义一个显示类型转换函数外,还可以提供一个隐式类型转换函数:

class Font{public:...operator FontHandle() const { return f; }  // 隐式类型转换...
};// 在调用时我们就可
Font f(getFont());
int newFontSize;changeFontSize(f, newFontSize);  // 将Font隐式转换为FontHandle

但这样的转换会增加错误发生的机会,例如

Font f1(getFont());FontHanle f2 = f1;  // 此时我们希望复制一个Font,但是f1可能隐式地转换为了FontHandle类型// 此时由f1管理的对象也可以通过f2直接取得,那么如果当f1或f2被销毁时,// 另外一个就悬空了

是否该提供一个显示转换函数将 RAII 类转换为其底部资源,或是应该提供隐式转换,答案主要取决于 RAII 类被设计执行的特定工作,以及它被使用的情况

最好是使用显示类型转换,因为它将 “非故意的类型转化” 的可能性最小化了,但是有时候隐式类型带来的方便也会更适用

请记住:

  • APIs 往往要求访问原始资源,所以每一个 RAII 类应该提供一个 “取得其所管理的资源” 的方法
  • 对原始资源的访问可能经由显示转换或隐式转换,一般来说显示转换比较安全,隐式转换比较方便

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

简单来说就是 new 一个数组的时候,别忘了要采用 delete [] 的形式

delete怎么知道它要删除的是单个对象还是一个对象数组呢。原因在于单一对象和对象数组的内存结构不同,对象数组的内存结构里面还存储了数组的长度(size),以便 delete 知道需要调用多少次析构函数,单一对象的内存则没有这条记录

  • 对一个单一对象调用 delete [] 形式的后果是没有定义的,通常会导致发生读异常
  • 对一个对象数组调用 delete 形式的后果也是没有定义的,通常会导致调用的析构函数过少

当你使用 typedef 关键字定义对象数组时要注意使用 delete [],例如:

typedef std::string AddressLines[4];std::string *pal = new AddressLines;  // 注意此时new返回的是一个数组delete pal;  // 错误,行为未定义
delete [] pal;  // 正确

为避免此类错误,最好不要对数组进行 typedef 动作

请记住:

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

以独立语句将 new 对象置入智能指针

考虑一个函数用来揭示处理程序的优先权,另一个函数用来在某动态分配所得的 Widget 上进行某些带有优先权的处理:

class Widget{public:explicit Widget();
};int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority());// 现在我们进行如下调用
processWidget(new Widget, priority());

这样调用会报错,因为 Widget 的构造函数是个 explicit ,不能进行隐式类型转换,此时我们可以

processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());

这样可以编译通过,但可能会发生内存泄漏的问题,原因在于 C++ 对函数传递参数的核算顺序并没有明确规定。上面这个函数有两个传入参数,第二个实参只是单纯的调用 priority 函数,但第一个实参由两步组成:

  • 第一步是new Widget;第二步是tr1::shared_ptr构造函数

于是编译器可能会采取以下的执行顺序:

  • 执行 “new Widget”
  • 执行 “priority”
  • 执行 “tr1::shared_ptr的构造函数”

如果在第二步执行 priority 的过程中发生错误,则 new 出来的指针将会遗失,因为它并没有被装入智能指针中,因为在 “资源被创建” 和 “资源被转换为资源管理对象” 两个时间点之间有可能发生异常干扰

解决办法就是将这两个实参的核算语句分离开,如:

std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());

这样就不会发生错误,因为编译器对于 “跨越语句的各项操作” 没有重新排列的自由。

请记住:

  • 以对立语句将 new 对象置入智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏

Effective C++ 3nd笔记——资源管理相关推荐

  1. Effective C++ 3nd 笔记——让自己习惯C++

    Effective C++ 3nd 笔记--让自己习惯C++ 尽量以const,enum,inline替换#define 宏定义在预处理阶段就进行了字符串替换,于是你的宏名有可能没进入符号表: 对于浮 ...

  2. Effective C++读书笔记 摘自 pandawuwyj的专栏

    Effective C++读书笔记(0)       Start   声明式(Declaration):告诉编译器某个东西的名称和类型,但略去细节.   std::size_t numDigits(i ...

  3. 《Effective C++》笔记

    <Effective C++>笔记 序言 条款01:视C++为一个语言联邦 条款02:尽量以const.enum.inline替换#define 条款03:尽可能使用const 条款04: ...

  4. Effective C++ 学习笔记 第七章:模板与泛型编程

    第一章见 Effective C++ 学习笔记 第一章:让自己习惯 C++ 第二章见 Effective C++ 学习笔记 第二章:构造.析构.赋值运算 第三章见 Effective C++ 学习笔记 ...

  5. more effective c++和effective c++读书笔记

    转载自http://bellgrade.blog.163.com/blog/static/83155959200863113228254/,方便日后自己查阅, More Effective C++读书 ...

  6. Effective Java读书笔记(二)

    Effective Java 读书笔记 (二) 创建和销毁对象 遇到多个构造器参数时要考虑使用构建器 创建和销毁对象 何时以及如何创建对象? 何时以及如何避免创建对象? 如何确保它们能够适时地销毁? ...

  7. Effective STL 读书笔记

    Effective STL 读书笔记 标签(空格分隔): 未分类 慎重选择容器类型 标准STL序列容器: vector.string.deque和list(双向列表). 标准STL管理容器: set. ...

  8. Java:Effective java学习笔记之 考虑实现Comparable 接口

    Java 考虑实现Comparable 接口 考虑实现Comparable 接口 1.Comparable接口 2.为什么要考虑实现Comparable接口 3.compareTo 方法的通用约定 4 ...

  9. unity 通过resouce加载图片_Unity游戏开发笔记-资源管理之资源加载

    资源加载是游戏中非常重要也非常繁琐的的一部分,不合理的资源管理,必定回给游戏的内存带来非常大的压力,尤其是一些重度游戏,不但资源特别多,引用关系特别复杂.维护一个不会内存泄漏而且加载效率高的资源加载框 ...

最新文章

  1. .NET 2.0 泛型在实际开发中的一次小应用
  2. c#的DateTime.Now函数详解
  3. springboot整合websocket实现群聊
  4. 【转】mybatis实战教程(mybatis in action)之八:mybatis 动态sql语句
  5. .NET-记一次架构优化实战与方案-梳理篇
  6. 电子书下载:Ultra-Fast ASP.NET 4.5 2nd
  7. 检查出某个文件的大小
  8. Java集合之LinkedList常见实例操作,实例说明
  9. python入门指南 许半仙txt-影帝的脑子坏了
  10. WPS 提示缺字体
  11. 解读思维导图(一)误区
  12. wenbao 与将linux系统(kali)装入U盘
  13. Shadow Mapping续
  14. java怎么模拟查询账户余额_spring boot + mybatis 模拟银行系统余额查询、转账、存取钱功能实现...
  15. BUUCTF---死亡之Ping详解
  16. pc炉石传说显示无法联觉服务器,《炉石传说》酒馆战棋尤朵拉船长怎么玩 尤朵拉船长运营思路一览...
  17. tx2上装scikit-image
  18. 关注小升初 | 中考分数线刷屏的背后是数千东昌家长学生的泪水
  19. 用python画卡通人物的画法_教你绘制扁平化风格的卡通人物肖像
  20. 计算机视觉(相机标定)-1.1-针孔摄像机透镜

热门文章

  1. 无人驾驶篇:环境感知
  2. iphone 绘图 虚线的画法
  3. 邪恶的韩国UMPC|UMID CLAMSHELL 更多图片
  4. python爬取搜狗图片
  5. vb6.0 __stdcall函数内调用__cdecl函数_mingw版
  6. 无线网络的加密方式:WEP、WPA和WPA2
  7. 数据分析 之 三大思路
  8. 拥抱机遇,夯实内功,拆解实体门店数字化转型中体验思维
  9. C++Test软件下载安装使用试用
  10. php接口限流实现方法