Effective C++ 读书笔记(3)

第三章 资源管理

本章共有如下5个条款:

条款13 以对象管理资源
条款14 在资源管理类中小心 coping 行为
条款15 在资源管理类中提供对原始资源的访问
条款16 成对使用 new 和 delete 时要采取相同形式
条款17 以独立语句将 newed 对象置入智能指针


条款13 以对象管理资源

以对象管理资源的两个关键想法:

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

看下面例子

class Investment {
	...//投资类型,继承体系中的root class
};
 
Investment* creatInvestment();//factory函数,返回指针,调用者要删除它
//利用creatAndDeleteInves函数建立和删除
void creatAndDeleteInves()
{
	Investment* pInv = creatInvestment();//调用factory函数
	...;
	delete pInv;//释放pInv所指对象
}

存在的问题:在“…”区域内,可能会提前return、抛出异常等没有执行到 delete pInv,则会造成资源的泄露。
为了保证资源总是得到释放,需要将资源放进对象内,当控制流离开 f ,该对象的析构函数会自动释放那些资源。
改进:利用auto_ptr智能指针,避免creatAndDeleteInves函数潜在的资源泄露可能性。

void creatAndDeleteInves()
{
	auto_ptr<Investment> pInv(creatAndDeleteInves());//调用factory函数
	...;//一些操作,经由auto_ptr的析构函数自动删除pInv
}

需要注意的是:为了预防 auto_ptr 同时指向一个对象时,被删除多次,auto_ptr有个性质:若通过 copy 构造函数或者 copy assignment 操作符复制它们,它们会变成null,而复制所得的指针获得资源的唯一拥有权。
目前 auto_ptr 的替代方案是 shared_ptr ,通过引用计数来持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除。

void creatAndDeleteInves()
{
	...;
	shared_ptr<Investment> pInv1(creatAndDeleteInves());//调用factory函数
	shared_ptr<Investment> pInv2(pInv1);//利用copy构造函数,pInv1和pInv2指向同一对象
	pInv1 = pInv2;//copy assignment操作符,无任何改变
	//pInv1和pInv2被销毁
} 

更多关于智能指针的内容可以参考这篇文章 C++11中智能指针的原理、使用、实现


条款14 在资源管理类中小心 coping 行为

并非所有资源都是heap-based,例如互斥锁对象Mutex, 则shared_ptr不适用,需要建立自己的资源管理类,在构造函数内获取资源,在析构函数内释放资源。

  • 复制 RAII 对象必须一并复制它所管理的资源,所以资源的 copying 行为决定 RAII 对象的 copying 行为;
  • 普遍的 RAII class copying 行为是:禁止复制(条款6提到:将 coping 操作声明为 private)、采用 shared_ptr 中的引用计数方法(reference counting)

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

条款13提到了通过资源管理类来处理和资源之间的互动,避免直接处理原始资源。但有些 API 会绕过资源管理对象,直接访问原始资源。

shared_ptr<Investment> pInv(createInvestment());
//假设以某个函数处理 Investment 对象
int daysHeld(const Investment* pi);//返回投资天数

下面的调用方式肯定是错误的:
int days = daysHeld(pInv);//错误
因为函数需要的是 Investment* 指针,你传递的是一个类型为shared_ptr<Investment>的对象。所以需要一个函数将 RAII 对象转换为内含的原始资源。
有两种方法:隐式转换和显式转换。
1)、显式转换
shared_ptr 和 auto_ptr 都提供了一个成员函数 get 返回内部的原始指针,这是显式转换。
int days = daysHeld(pInv.get()); //没有问题
2)、隐式转换
shared_ptr 和 auto_ptr 都重载了操作符 operator->和operator*,这样就允许隐式转换到原始指针。
举例:假设 Investment 类有个成员函数bool isTaxFree() const;那么下面的调用是OK的

bool taxable1 = !(pInv->isTaxFree());      //经由operator->访问资源
bool taxable2 = !((*pInv).isTaxFree());     //经由operator*访问资源

现在的问题是,需要原始指针的地方(例如,函数形参),如何以智能指针代替?解决方法是:提供一个隐式转换函数。这里不再举例子了。
两条建议:

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

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

new 的过程:1、分配内存(operator new);2、调用一个或多个构造函数建立对象
delete 过程:1、一个或多个析构函数被调用;2、释放内存(operator delete)

string* str1 = new string;//一个string对象
string* str2 = new string[100];//100个string对象组成的数组
delete str1;
delete [] str2;

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


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

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

processWidget(shared_ptr<Widget>(new Widget), priority());

在调用 processWidget函数之前,编译器必须创建代码,做三件事情:调用priority、执行new Widget、调用shared_ptr构造函数。但编译器会以怎样的次序完成这些事情呢?可以确定的是new Widget一定执行于shared_ptr构造函数被调用之前,但对priority的调用则可以排在第一或第二或第三执行。
当编译器按这样序列操作时:执行new Widget—>调用priority—>调用shared_ptr构造函数,如果对priority函数调用异常,则new Widget返回的指针会遗失,因为它尚未被置入shared_ptr内。
解决方法:使用分离语句

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

所以,要以独立语句将 newed 对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。


参考文章

1、https://blog.csdn.net/vict_wang/article/details/81637048

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奔跑的贝塔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值