《Effective C++》第三版-3. 资源管理(Resource Management)
- 条款13:以对象管理资源(Use objects to manage resources)
- 条款14:在资源管理类中小心copying行为(Think carefully about copying behavior in resource-managing classes)
- 条款15:在资源管理类中替工对原始资源的访问(Provide access to raw resources in resource-managing classes)
- 条款16:成对使用new和delete时要采取相同形式(Use the same form in corresponding uses of new and delete)
- 条款17:以独立语句奖newed对象置入智能指针(Store newed objects in smart pointers in standalone statements)
前几章的笔记多有不足,这一章会持续改进
条款13:以对象管理资源(Use objects to manage resources)
关键想法
考虑以下易出错的例子:
class Investment { ... }; //投资类型继承体系中的root类
//工厂函数,指向Investment继承体系内的动态分配对象,参数省略
Investment* createInvestment {};
void f()
{
Investment* pInv = createInvestment(); //调用工厂函数
... //若这里return则无法执行delete
delete pInv; //释放pInv所指对象
}
解决方案:把资源放进对象,可利用析构函数自动调用机制确保资源释放
以对象管理资源的两个关键想法:
- 获得资源后立刻放进管理对象(managing object)内
- 资源取得时机便是初始化时机(Resource Acquisition Is Initialization,RAII)
- 有时获得的资源会拿来赋值而非初始化
- 管理对象运用析构函数确保资源释放
- 不论控制流如何离开区块,一旦对象被销毁(如离开对象作用域)其析构函数会自动调用
智能指针
auto_ptr:
- 通过copy构造函数或copy assignment操作符复制它们,它们会变成null,复制所得的指针将取得资源的唯一所有权
- 故需要元素能够复制地STL容器不兼容auto_ptr
auto_ptr在C++11中已被弃用,以下简要介绍
void f()
{
std::auto_ptr<Investment> pInv(createInvestment());
...
} //auto_ptr的析构函数自动删除pInv
std::auto_ptr<Investment< pInv1(createInvestment());
std::auto_ptr<Investment< pInv2(pInv1); //现在pInv2指向对象,pInv1为null
pInv1 = pInv2; //现在pInv1指向对象,pInv2为null
shared_ptr:属于引用计数型智慧指针(reference-counting smart pointer,RCSP)
- 会持续追踪有多少对象指向某笔资源,并在无人指向它时自动删除该资源
- 其行为类似垃圾回收(garbage collection),但RCSP无法打破环状引用(cycles of references,如两个未被使用的对象彼此互指)
- 适用于STL容器
void f()
{
std::tr1::shared_ptr<Investment> pInv(createInvestment());
...
} //shared_ptr的析构函数自动删除pInv
void f()
{
...
std::tr1::shared_ptr<Investment> pInv1(createInvestment());
std::tr1::shared_ptr<Investment> pInv2(pInv1); //指向同一个对象
pInv1 = pInv2; //同上
...
} //pInv1和pInv2被销毁,他们所指的对象也被销毁
auto_ptr和shared_ptr的析构函数做delete而非delete[],故不适合在动态分配而得的array身上使用,即使能通过编译
Boost中boost::scoped_array和boost::shared_array则可用于数组且类似auto_ptr和shared_ptr
std::auto_ptr<std::string> aps(new std::string[10]); //会调用错误形式的delete
std::tr1::shared_ptr<int> spi(new int[1024]); //同上
Tips:
- 为防止资源泄漏,请使用RAII对象,其在构造函数中获得资源并在析构函数中释放资源
- RAII中常用shared_ptr,其copy行为比较直观(auto_ptr已被弃用)
条款14:在资源管理类中小心copying行为(Think carefully about copying behavior in resource-managing classes)
对mutex一点不了解emmm,硬着头皮总结下
非heap-based的资源不适合使用智能指针作为资源掌管者(resource handlers)
考虑使用C API函数处理类型为Mutex的互斥器对象(mutex objects),共有lock和unlock两函数可用。为确保不会忘记把被锁的Mutex解锁,可建立类以管理机锁,该类的结构符合RAII守则
void lock(Mutex* pm); //锁定pm所指的互斥器
void unlock(Mutex* pm); //解除互斥器的锁定
//管理机锁的类,符合RAII守则
class Lock {
public:
explicit Lock(Mutex* pm)
: mutexPrt(pm)
{ lock(mutexPtr); }
~Lock() { unlock(mutexPtr); }
private:
Mutex *mutexPtr;
};
//客户对Lock的用法符合RAII方式
Mutex m;
...
{
Lock ml(&m);
...
}
如果要复制Lock对象,则可能:
Lock m11(&m); //锁定m
Lock m12(m11); //将m11复制到m12上
- 禁止复制
- 很多时候允许RAII对象复制不合理(则应将copying操作声明为private)、
- 但Lock类是少有的能合理拥有同步化基础器物(synchronization primitives)的副本,其可能可以被复制
- 对底层资源使用引用计数法
- 和RAII规则类似,但是当使用上一个Mutex时需要解除锁定而非删除
- tr1::shared_ptr允许指定删除器(deleter),其为函数或函数对象,当引用次数为0时调用
class Lock {
public:
explicit Lock(Mutex* pm) //以Mutex初始化shared_ptr
: mutexPtr(pm, unlock) //以unlock函数作为删除器
{
lock(mutexPtr.get());
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr; //使用shared_ptr替换raw pointer
};
- 复制底部资源,即进行深拷贝(deep copying),如将指针及其所指对象都复制
- 转移底部资源的拥有权
- 少数情况需要确保只有一个RAII指向一个未加工资源(raw resource),即使RAII对象被复制
- 此时资源的所有权会从被复制物转移到目标物
Tips:
- 复制RAII对象必须一并复制它所管理的资源,故资源的copying行为决定RAII对象的copying行为
- 通常的RAII类的copying行为是:抑制copying、使用引用计数法。但其他行为也可能实现
条款15:在资源管理类中替工对原始资源的访问(Provide access to raw resources in resource-managing classes)
由于auto_ptr已弃用,本条款不整理和其相关的内容
显示转换或隐式转换
有时智能指针不能直接使用(如下例子),需要显示转换或隐式转换:
class Investment {
public:
bool isTaxFree() const;
...
}
Investment* createInvestment(); //工厂函数
std::tr1::shared_ptr<Investment> pInv(createInvestment());
int daysHeld(const Investment* pi); //返回投资天数
int days = daysHeld(pInv); //错误!daysHeld需要Investment*而非tr1::shared_ptr
//显示转换
int days = daysHeld(pInv.get());
//隐式转换,tr1::share_ptr重载了指针取值(pointer dereferencing)操作符(->和*)
bool taxable1 = !(pInv->isTaxFree());
bool taxable2 = !((*pInv).isTaxFree());
优缺点
有时必须取得RAII对象内的原始资源,考虑用于字体的RAII类:
FontHandle getFont(); //这是C API,省略参数
void releaseFont(FontHandle fh); //来自同一组
class Font { //RAII类
public:
explicit Font(FontHandle fh) //获得资源
: f(fh) //使用pass-by-value,因为C API这样做
{ }
~Font() { releaseFont(f); } //释放资源
private:
FontHandle f; //原始字体资源
};
- 显示转换:可读性强,但是需要API时必须调用get
class Font {
public:
...
FontHandle get() const { return f; } //显式转换函数
...
};
void changeFontSIze(FontHandle f, int newSize);
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize);
- 隐式转换:调用C API更自然,但是易出错
- 可能在需要Font时意外创建FontHandle,且f被销毁则f0成为悬空的(dangle)
class Font { //RAII类
public:
...
operator FontHandle() const { return f; } //隐式转换函数
...
};
changeFontSize(f, newFontSize)
FontHanle f0 = f; //想要拷贝,但是将f1隐式转换为其底部的FontHandle才复制
Tips:
- API往往要求访问原始资源,故RAII类应提供取得其管理的资源的方法
- 对原始资源的访问包含显示转换和隐式转换,一般显示转换安全而隐式转换方便
条款16:成对使用new和delete时要采取相同形式(Use the same form in corresponding uses of new and delete)
std::string* stringArray = new std::string[100];
...
delete stringArray
以上程序的行为不明确:
- 使用new时会发生两件事:
- 内存分配
- 调用针对此内存的构造函数
- delete需要知道被删除的内存内有多少对象,其决定了要调用多少析构函数
- 若指针指向数组对象,则数组所用的内存包含数组大小信息
- 若指针指向单一对象,则无上述信息
- 需要人为告诉delete所删除的对象类型
std::string* stringPtr1 = new std::string;
std::string* stringPtr2 = new std::string[100];
...
delete stringPtr1; //删除对象
delete [ ] stringPtr2; //删除对象组成的数组
使用typedef需要考虑相同的问题(下例中数组使用typedef并不合适,容易产生错误,仅做说明):
typedef std::string AddressLines[4]; //地址有4行,每行一个string
std::string* pal = new AddressLines; //本质上同new string[4]
delete pal; //错误!行为未有定义
delete [ ] pal; //正确
Tips:
- 如果new加上[],则delete必须加上[];如果new没加[],则delete不能加[]
条款17:以独立语句奖newed对象置入智能指针(Store newed objects in smart pointers in standalone statements)
考虑涉及优先权的例子:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
//错误!new Widget无法隐式转换为shared_ptr,因为shared_ptr的构造函数为explicit
processWidget(new Widget, priority());
//可以通过编译,但可能泄漏资源
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
编译器产出processWidget调用码之前,必须首先核酸即将被传递的各个实参,其要做事情有三件:
- 第一实参
- 执行new Widget
- 调用tr1::shared_ptr构造函数
- 第二实参
- 调用priority
实际执行的次序弹性很大,只能确定执行new Widget一定先于调用tr1::shared_ptr构造函数,但调用priority的次序不一定。若编译器选择以下次序:
- 执行new Widget
- 调用priority
- 调用tr1::shared_ptr构造函数
则如果调用priority出现异常,那new Widget返回的指针将遗失,其未来得及放入tr1::shared_ptr内,进而导致资源泄漏。即创建资源和资源转换为资源管理对象直接有可能发生异常干扰
解决方法:使用分离语句,因为编译器对于跨越语句的各项操作没有重新排列的自由
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority()); //这样调用不会泄漏
Tips:
- 以独立语句将newed对象存储于智能指针内,否则一旦有异常则可能发生隐秘的资源泄漏
《Effective C++》第三版-3. 资源管理(Resource Management)的更多相关文章
- 《Effective Java 第三版》新条目介绍
版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...
- C++学习书籍推荐《Effective C++ 第三版》下载
百度云及其他网盘下载地址:点我 编辑推荐 <Effective C++:改善程序与设计的55个具体做法(第3版)(中文版)(双色)>前两个版本抓住了全世界无数程序员的目光.原因十分明显:S ...
- Effective Java第三版(一) ——用静态工厂代替构造器
此文做为<Effective Java>系列的第一篇,所以有必要大概说下此书的特点,当然很多人可能都看过,毕竟是有着Java四大名著之一的大名在外,不过总会有萌新不了解,例如我!<E ...
- effective java(第三版)---读书笔记
第一章 引言 < Effective Java>这本书并不厚,而且并不适合初学者,适合有一定的工作经验的java攻城狮.这本书不是百科全书式的JAVA 手册,而是试图在讲述如何正确.高效地 ...
- 《Effective Java 第三版》目录汇总
经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...
- C++学习书籍推荐《Effective C++ 第三版(英文)》下载
百度云及其他网盘下载地址:点我 作者简介 Scott Meyers is one of the world's foremost authorities on C++, providing train ...
- Effective Java 第三版——9. 使用try-with-resources语句替代try-finally语句
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——1. 考虑使用静态工厂方法替代构造方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——3. 使用私有构造方法或枚类实现Singleton属性
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——7. 消除过期的对象引用
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
随机推荐
- Java SM2
pom <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http:// ...
- 手把手带你用香橙派AIpro开发AI推理应用
本文分享自华为云社区<如何基于香橙派AIpro开发AI推理应用>,作者:昇腾CANN. 01 简介 香橙派AIpro开发板采用昇腾AI技术路线,接口丰富且具有强大的可扩展性,提供8/20T ...
- WARN o.a.t.util.scan.StandardJarScanner - Failed to scan [file:/D:/Mavencangku/com/sun/xml/bind/jaxb-core/2.3.0/jaxb-api.jar] from classloader hierarchy
1.SpringBoot项目启动突然报错 2024-03-27 14:57:41 [restartedMain] WARN o.a.t.util.scan.StandardJarScanner - F ...
- 第一个hello驱动
Linux驱动程序的分类 字符设备驱动.块设备驱动和网络设备驱动. Linux驱动程序运行方式 把驱动程序编译进内核里面,这样内核启动后就会自动运行驱动程序了: 把驱动程序编译成以.ko为后缀的模块文 ...
- #树形依赖背包,点分治#BZOJ 4182 Shopping
题目 给定一棵大小为 \(n\) 的树,每个点代表一种物品,其具有体积.价值和数量的属性, 现在选择一个连通块,使得里面所有点都被选中且体积不超过 \(m\),问最大价值. \(n\leq 500,m ...
- #裴蜀定理#洛谷 2520 [HAOI2011]向量
题目 分析 首先若 \(a,b\) 都为 0 要特判. 若 \(\begin{cases}x=pa+qb+p'a+q'b\\y=qa+pb-q'a-p'b\end{cases}\) 合并同类项可以得到 ...
- #树状数组,欧拉函数#CF594D REQ
题目 给定 \(n\) 个数,求 \(\varphi(\prod_{i=l}^r{a_i})\) 分析 考虑单个欧拉函数的求法,只需要求出这个数的质因数计算即可. 那么考虑离线,枚举右端点,记录每个质 ...
- Jetty的模块
查看模块的列表,执行如下命令: java -jar $JETTY_HOME/start.jar --list-modules 启用模块,比如http模块,执行如下命令: java -jar $JETT ...
- 医疗BI系统如何使医疗行业完成精细化管理转型?
不久前在北京召开的全国医疗管理工作会议,确定了今年的医疗管理工作重点.会议强调,推动医疗管理改革工作的过程中要对形势.规律准确把握,积极应对可能面临的挑战,以"三个转变.三个提高" ...
- 前端使用 Konva 实现可视化设计器(3)
github/gitee Star 终于有几个了! 从这章开始,难度算是(或者说细节较多)升级,是不是值得更多的 Star 呢?! 继续求 Star ,希望大家多多一键三连,十分感谢大家的支持~ 创作 ...