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

对于这一节的内容,其实自己理解的一般。这一章主要阐述了一些理论和比较建议的做法,但是并没有给出太多的案例供人理解。
个人认为学习这一章应该搞清楚以下的事情

  • 为什么需要小心拷贝行为?
  • 拷贝行为会导致什么严重问题?
  • 小心拷贝行为,一般的应对方案就是不支持拷贝吗?
  • 如果一定要拷贝,需要怎么做?
  • 对应提出的集中方案,分别是什么情况下使用呢?

结论:

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

前面条款13,提出了RAII(Resource Acquisition is Initialization;RAII),也就是资源取得时机便是初始化时机,并以此作为“资源管理类”的脊柱,也描述了auto_ptrshared_ptr如何将这个概念表现在heap-based 资源上。然而并不是所有资源都是heap-based,对于那种资源而言,像auto_ptr和shared_ptr这样的管理指针往往不适合作为资源管理者。这时,我们需要建立自己的资源管理类。

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

void lock(Mutex* pm);		// 锁定pm所指的互斥器
void unlock(Mutex* pm);		// 将互斥器解除锁定

为了确保绝不会忘记将一个被锁住的Mutex解锁,你可能会希望建立一个class来管理机锁。该机制也就是“资源在构造期间获得,在析构期间释放”。

class Lock
{
public:
	explicit Lock(Mutex* pm) : mutexPtr(pm)
	{ lock(mutexPtr); }				// 获得资源Lock() {unlock(mutexPtr); }	// 释放资源
private:
	Mutex *mutexPtr;
};

客户对Lock的用法符合RAII方式:

Mutex m;		// 定义你需要的互斥器
...
{				// 建立一个区块用来定义critical section
Lock m1(&m);	// 锁定互斥器
...				// 执行critical section内的操作
}				// 在区块最末尾,自动解除互斥器锁定

以上代码很合理,但是如果Lock对象被复制,会发生什么事?

Lock m11(&m);	// 锁定m
Lock m12(&m11);	// 将m11复制到m12身上,这回发生什么事?

以上反映的问题是:当一个RAII对象被复制,会发生什么事?大多数情况下你会选择以下两种解决方案

  • 禁止复制。许多时候允许RAII对象被复制并不合理。对一个像Lock这样的class这是由可能的,因为很少能够合理拥有“同步化基础器物”(syncchronization primitives)的复件(副本)。如果复制动作对RAII 对象并不合理,你便应该禁止:将copying操作声明为private。对Lock看起来是这样的:
class Lock : private Uncopyable		//禁止复制。见条款6
{
public:
	...								// 如前
};
  • 对底层资源使用“引用计数法”。有时候我们希望保有资源,直到它的最后一个使用者(某对象)被销毁。这种情况下复制RAII对象时,应该将资源的“被引用数”递增。shared_ptr便是如此。

通常只要内含一个shared_ptr成员变量,RAII class便可实现出reference-counting copying行为。如果前述的Lock打算使用 reference counting,他可以改变mutexPtr的类型,将它从Mutex* 改为 shared_ptr。然而很不幸shared_ptr的缺省行为是“当引用次数为0时闪促其所指物”,那不是我们想要的行为。当我们用上一个Mutex,我们想要做的释放动作是解锁定而非删除。
幸运的是shared_ptr允许指定所谓的“删除器”(deleter),那是一个函数或函数对象,当引用次数为0时便被调用。删除器对shared_ptr构造函数而言是可有可无的第二参数,所以代码看起来像这样:

class Lock
{
public:
	explicit Lock(Mutex* pm)	// 以某个Mutex初始化shared_ptr
		: mutexPtr(pm, unlock)	// 并以unlock函数为删除器
	{
		lock(mutexPtr.get())	// get函数返回一个指向原声指针的函数
	}
private:
	std::tr1::shared_ptr<Mutex> mutexPtr; // 使用shared_ptr替换new pointer
}

请注意,本例中的Lock class不再声明析构函数。因为没有必要。class析构函数会自动调用其non-static成员变量(本例为mutexPtr)的析构函数。而mutexPtr的析构函数会在互斥器的引用次数为0时自动调用shared_ptr的删除器(本例为unlock)。

  • 复制底层资源。有时候,只要你喜欢,可以针对一份资源拥有其任意数量的复件(副本)。而你需要“资源管理类”的唯一理由是,当你不再需要某个复件时确保它被释放。在此情况下,复制资源管理对象,应该同时也复制其所包含的资源。也就是说,复制资源管理对象时,进行的是“深度拷贝”。
    某些标准字符串类型是由“指向heap内存”的指针构成。这种字符串对象内含一个指针指向一块heap内存。当这样一个字符串对象被复制,不论指针或其所指内存都会被制作出一个复件。这样的字符串展现深度复制行为。
  • 转移底部资源的拥有权。某些罕见场合下,你可能希望确保永远只有一个RAII对象指向一个未加工资源,即使RAII对象被复制依然如此。此时,资源的拥有权会从被复制物转移到目标物。这是auto_ptr复制时奉行的原则。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值