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

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

  • 资源管理类很好,它们是对抗资源泄露的堡垒。
  • 但许多 APIs 直接指涉资源,只得资源管理对象直接访问原始资源。

举个例子,条款 13 导入一个观念:使用智能指针如 auto_ptr 或 tr1::shared_ptr 保存 factory 函数如 createInvestment 的调用结果:

std::tr1::shared_ptr<Investment> pInv(createment());  	// 见条款 13

假设你希望以某个函数处理 Investment 对象,像下面这样:

int daysHeld(const Investment* pi);   // 返回投资天数

若你想要这样调用它:

int days = daysHeld(pInv);			// 错误!

不能通过编译,因为 dayHeld 需要的是 Investment* 的指针,而 pInv 却是个类型为 tr1::shared_ptr< Investment >的对象。
这时候你需要一个可将 RAII class 对象(本例为 tr1::shared_ptr)转换为其所含之原始资源(本例为底部之 Investment*)的函数。有两种方法:显式和隐式转换。
① tr1::shared_ptr 和 auto_ptr 都提供一个 get 成员函数,用来执行显式转换,也就是它会返回智能指针内部的原始指针(的复件):

int days = daysHeld(pInv.get());			// 将 pInv 内的原始指针传给 dayHeld

② (几乎)所有智能指针一样,tr1::shared_ptr 和 auto_ptr 也重载了指针取值(pointer dereferencing)操作符(operator-> 和 operator*),它们允许隐式转换至底部指针:

class Investment {												// investment 继承体系的根类
public:
	bool isTaxFree() const;										// 原始资源
	...
};
Investment* createInvestment();			                     	// factory 函数
std::tr1::shared_ptr<Investment> pi1(createInvestment());		// 令 tr1::shared_ptr 管理一笔资源
bool taxable1 = !(pi1->isTaxFree());		    				// 经由 operator-> 访问资源
...
std::auto_ptr<Investment> pi2(createInvestment());		 		// 令auto_ptr 管理一笔资源
bool taxable2 = !((*pi2).isTaxFree());							// 经由 operator* 访问资源

由于有时候还是必须取得 RAII 对象内的原始资源,做法是提供一个隐式转换函数。
考虑下面这个例子,用于字体的 RAII class(对 C API 而言字体是一种原生数据结构):

FontHandle getFont(); 							// 这是个 C API
void releaseFont(FontHandle fh);				// 来自同一组 C API
class Font {
public:
	explicit Font(FontHandle fh): f(fh)			// 获得资源,采用值传递,因为 C API 这样做
	{ }
	~Font() { releaseFont(f); }					// 释放资源
private:
	FontHandle f;								//原始字体资源
};

假设有大量与字体相关的 C API,它们处理的是 FontHandles,那么 “ 将 Font 对象转换为 FontHandle ” 会是一种很繁琐的需求。Font class 可为此提供一个显式转换函数,像 get 那样:

class Font {
public:
	...
	FontHandle get() const { return f; }		// 显式转换函数,主要为了防止资源泄露
	...
};

不幸的是这使得用户每当想要使用 API 时就必须调用 get :

void changeFontSize(FontHandle f, int newSize);			// C API
Font f(getFont());
int newSize;
...
changeFontSize(f.get(), newFontSize);			// 明白地将 Font 转换为 FontHandle

另一个办法是令 Font 提供隐式转换函数,转型为 FontHandle:

class Font {
public:
	...
	operator FontHandle() const			// 隐式转换函数
	{ return f; }
	...
};

这使得用户调用 C API 时轻松而自然:

Font f(getFont());
int newFontSize;
...
changeFontSize(f, newFontSize);		// 将 Font 隐式转换为 FontHandle

但这个隐式转换会增加错误发生的机会。例如用户可能会在需要 Font 时意外创建一个 FontHandle:

Font f1(getFont());
...
FontHandle f2 = f1;					// 啊哦,原意是要拷贝一个 Font 对象
									// 却反而将 f1 隐式转换为其底部的 FontHandle,然后才复制它

两种方法各有优缺点,最佳的设计是:让接口容易被正确使用,不易被误用

请记住:

  • APIs 往往要求访问原始资源,所以每一个 RAII classes 应该提供一个 “ 取得其所管理资源 ” 的方法。
  • 对原始资源的访问可能经由显式或隐式转换。一般而言显式转换比较安全,但隐式转换对用户比较方便。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值