Effective C++ 条款13_不止于此

以对象管理资源

假设我们使用一个用来墅模投资行为(例如股票、债券等)的程序库,其中各种各样的投资类型继承自一个 root class Investment:

class Investment { ... };  // root class

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

Investment* createInvestment();   // 返回指针,指向Investment 继承体系内的动态分配对象。调用者有责任删除它

如上面注释所言,createInvestment 的调用端使用了函数返回的对象后,有责任删除它。现在考虑有个 f 函数来履行这个责任:

void f(){
	Investment* pInv = createInvestment();    // 调用 factory 函数
	...
	delete pInv;		// 释放 pInv 所指对象
}

这样看起来妥当,但若干情况下 f 可能无法删除它得自 createInvestment 的投资对象:
① “ … ” 区域内的一个过早的 return 语句结束了函数。
② delete 语句用于循环内,但是遇到了 continue 或 goto 语句过早退出。
③ “ … ” 区域内的语句抛出异常,控制流将不会再降临 delete。

当然,谨慎地编写程序可以防止这类错误,但是随着时间渐渐过去代码可能被修改。一但软件开始接受维护,可能会有某些人加入了 return 或 continue 而导致了资源内存泄露等问题是行不通的。

为确保 createInvestment 返回的资源总是被释放,我们需要将资源放进对象内,当控制流离开 f ,该对象的析构函数会自动释放那些资源。
许多资源被动态分配于 heap 内而后被用于单一区块或函数内。它们应该在控制流离开那个区块或函数时被释放。
标准的程序库提供的 auto_ptr 正是针对这种形势而设计的特质产品。
auto_ptr 是个 “ 类指针(pointer-like)对象 ”,也就是所谓的 “ 智能指针 ”,其析构函数自动对其所指对象调用 delete。下面示范如何使用 auto_ptr 以避免 f 函数潜在的资源泄露可能性。

void f(){
	std::auto_ptr<Investment> pInv(createInvestment());   // 调用 factory 函数,一如既往使用 pInv
															// 经由 auto_ptr 的析构函数自动删除 pInv
	...
}

这个简单的例子示范了 “ 以对象管理资源 ” 的两个关键想法:

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

实际上 “ 以对象管理资源 ” 的观念常被称为 “ 资源取得时机便是初始化时机 ” (Resource Acquisition Is Initialization;RAII)。

由于 auto_ptr 被销毁时会自动删除它所指之物,所以一定要注意别让多个 auto_ptr 同时指向同一对象。如果真那样,对象会被删除一次以上,这会使你的程序搭上驶向 “ 未定义行为 ” 的快速列车上。为了预防这个问题,auto_ptrs 有一个不同寻常的性质:若通过 copy 构造函数或 copy assignment 操作符复制它们,它们会变成 null,而复制所得的指针将取得资源的唯一拥有权!

std::auto_ptr<Investment> pInv1(createInvestment());   // pInv1 指向 createInvestment 返回物
std::auto_ptr<Investment> pInv2(pInv1);			// 现在 pInv2 指向对象,pInv1 被设为 null
pInv1 = pInv2;                                	// 现在 pInv1 指向对象,pInv2 被设为 null

这一诡异的复制行为,附加上其底层条件:“ 受 auto_ptrs 管理的资源必须绝对没有一个以上的 auto_ptr 同时指向它 ”,意味 auto_ptrs 并非管理动态分配资源的神兵利器。
举个栗子,STL 容器要求其元素发挥 “ 正常的 ” 复制行为,因此这些容器容不得 auto_ptr。

auto_ptr 的替代方案是 “ 引用计数型智慧指针 ” (PCSP)。所谓 PCSP 的功能就是持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。但它不能打破环状引用,例如两个其实已经没有使用的对象彼此互指,因而好像还处于 “ 被使用 ” 状态。

TR1 的 tr1::shared_ptr(见条款54)就是个PCSP,所以可以改造一下 f :

void f(){
	...
	std::tr1::shared_ptr<Investment> pInv(createInvestment());    // 调用 factory 函数,使用 pInv 一如既往
																// 经由 shared_ptr 析构函数自动删除 pInv
	...
}

这段代码看起来和使用 auto_ptr 的那个版本几乎相同,但是 shared_ptr 的复制行为就正常多了:

void f(){
	...
	std::tr1::shared_ptr<Investment> pInv1(createInvestment());  // pInv1 指向 createInvestment 返回物
	std::tr1::shared_ptr<Investment> pInv2(pInv1);				// pInv1 和pInv2 指向同一个对象
	pInv1 = pInv2;												// 无变化
	...											// pInv1 和 pInv2 被销毁,它们所指的对象也被自动销毁
}

由于 tr1::shared_ptrs 的复制行为 “ 一如预期 ”,它们可被用于 STL 容器以及其他 “ auto_ptr 之非正统复制行为并不适用 ” 的语境上。

最后请记住:

  • 为防止资源泄露,请使用 RAII 对象,它们在构造函数中获得资源并在析构函数中释放资源。
  • 两个常被使用的 RAII classes 分别是 tr1::shared_ptr 和 auto_ptr。前者通常是较佳选择,因为其 copy 行为比较直观。若选择 auto_ptr,复制动作会使它(被赋值物)指向 null。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值