您的位置:首页 > 编程语言 > C语言/C++

[翻译] Effective C++, 3rd Edition, Item 14: 谨慎考虑 resource-managing classes(资源管理类)中的拷贝行为

2005-07-17 23:27 671 查看
[b]Item 14: 谨慎考虑 resource-managing classes(资源管理类)中的拷贝行为[/b]

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.csdn.net/fatalerror99/

Item 13 介绍了作为 resource-managing classes(资源管理类)支柱的 Resource Acquisition Is Initialization (RAII) 原则,并描述了 auto_ptr 和 tr1::shared_ptr 在 heap-based(基于堆)的资源上运用这一原则的表现。然而,并非所有的资源都是 heap-based(基于堆)的,而对于这样的资源,像 auto_ptr 和 tr1::shared_ptr 这样的 smart pointers(智能指针)通常就不像 resource handlers(资源句柄)那样合适。在这种情况下,有时,你很可能要根据你自己的需要去创建你自己的 resource-managing classes(资源管理类)。

例如,假设你使用一个 C API 提供的 lock 和 unlock 函数去操纵 Mutex 类型的 mutex objects(互斥体对象):

void lock(Mutex *pm); // lock mutex pointed to by pm

void unlock(Mutex *pm); // unlock the mutex

为了确保你从不会忘记解锁一个被你加了锁的 Mutex,你案打算创建一个 class 来管理锁。这样一个 class 的基本结构被 RAII 原则规定,通过 construction(构造函数)获取资源并通过 destruction(析构函数)释放它:

class Lock {
public:
explicit Lock(Mutex *pm)
: mutexPtr(pm)
{ lock(mutexPtr); } // acquire resource

~Lock() { unlock(mutexPtr); } // release resource

private:
Mutex *mutexPtr;
};

客户按照惯常的 RAII 风格来使用 Lock:

Mutex m; // define the mutex you need to use
...
{ // create block to define critical section
Lock ml(&m); // lock the mutex
... // perform critical section operations

} // automatically unlock mutex at end
// of block

这没什么问题,但是如果一个 Lock object 被拷贝应该发生什么?

Lock ml1(&m); // lock m

Lock ml2(ml1); // copy ml1 to ml2—what should
// happen here?

这是一个更一般的问题的特定实例,每一个 RAII class 的作者都必须面对它:当一个 RAII object 被拷贝的时候应该发生什么?大多数情况下,你需要从下面各种可能性中挑选一个:

prohibit copying(禁止拷贝)。在很多情况下,允许 RAII objects 被拷贝是没有意义的。这对于像 Lock 这样 class 很可能是正确的,因为同步原本的“拷贝”很少有什么意义。当拷贝对一个 RAII class 没有什么意义的时候,你应该禁止它。Item 6 解释了如何做到这一点:声明拷贝操作为私有。对于 Lock,看起来可能就像这样:

class Lock: private Uncopyable { // prohibit copying — see
public: // Item 6
... // as before
};

reference-count the underlying resource(对底层的资源引用计数)。有时人们需要的是保持一个资源直到最后一个使用它的 object 被销毁。在这种情况下,拷贝一个 RAII object 应该增加引用这一资源的 objects 的数目。这也就是被 tr1::shared_ptr 使用的 "copy"(“拷贝”)的含意。

通常,RAII classes 只需要包含一个 tr1::shared_ptr data member(数据成员)就能够实现 reference-counting(引用计数)的拷贝行为。例如,如果 Lock 要使用 reference counting(引用计数),他可能要将 mutexPtr 的类型从 Mutex* 变为 tr1::shared_ptr<Mutex>。不幸的是,tr1::shared_ptr 的缺省行为是当它所指向的东西的 reference count(引用计数)变为零的时候将它删除,但这不是我们想要的。当我们使用完一个 Mutex 后,我们想要将它解锁,而不是将它删除。

幸运的是,tr1::shared_ptr 允许有一个对 "deleter"(当 reference count(引用计数)变为零时调用的一个 function(函数)或者 function object(函数对象))的特殊说明。(这一功能是 auto_ptr 所没有的,auto_ptr 总是删除它的 pointer(指针)。)deleter 是 tr1::shared_ptr 的 constructor(构造函数)的可选的第二个参数,所以,代码看起来就像这样:

class Lock {
public:
explicit Lock(Mutex *pm) // init shared_ptr with the Mutex
: mutexPtr(pm, unlock) // to point to and the unlock func
{ // as the deleter

lock(mutexPtr.get()); // see Item 15 for info on "get"
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr; // use shared_ptr
}; // instead of raw pointer

在这个例子中,注意 Lock class 是如何不再声明一个 destructor(析构函数)的。那是因为它不需要。Item 5 解释了一个 class 的 destructor(析构函数)(无论它是 compiler-generated(编译器生成)的还是 user-defined(用户定义)的)会自动调用这个 class 的 non-static data members(非静态数据成员)的 destructors(析构函数)。在本例中,就是 mutexPtr。但是,当 mutex(互斥体)的 reference count(引用计数)变为零时,mutexPtr 的 destructor(析构函数)会自动调用 tr1::shared_ptr 的 deleter ——在此就是 unlock。(看过这个 class 的源代码的人多半意识到需要增加一条注释表明你并非忘记了 destruction(析构),而只是依赖 compiler-generated(编译器生成)的缺省行为。)

copy the underlying resource(拷贝底层的资源)。有时就像你所希望的你可以拥有一个资源的多个拷贝,你需要一个 resource-managing class(资源管理类)的唯一理由就是确保当你使用完每一个拷贝之后,它都会被释放。在这种情况下,拷贝一个 resource-managing object(资源管理对象)也应该同时拷贝它所包覆的资源。也就是说,拷贝一个 resource-managing object(资源管理对象)需要实行一次 "deep copy"(“深层拷贝”)。

某些标准 string 类型的实现是由 pointers to heap memory(指向堆内存的指针)构成,那里存储着组成那个 string(字符串)的字符。这样的 strings 的 objects 包含一个 pointers to heap memory(指向堆内存的指针)。当一个 string object 被拷贝,一个拷贝应该由那个指针和它所指向的内存两者组成。这样的 strings 表现为 deep copying(深层拷贝)。

transfer ownership of the underlying resource(传递底层资源的所有权)。在非常特殊的场合,你可能希望确保只有一个 RAII object 引用一个 raw resource(裸资源),而当这个 RAII object 被拷贝的时候,资源的所有权从 copied object(被拷贝对象)传递到 copying object(拷贝对象)。就像 Item 13 所说明的,这就是被 auto_ptr 使用的 "copy"(“拷贝”)的含意。

copying functions(拷贝函数)(copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符))可能由编译器生成,所以除非 compiler-generated(编译器生成)的版本所做的事正是你想要的(Item 5 说明了这些缺省行为),否则你应该自己编写它们。在某些情况下,你还要支持这些函数的泛型化版本。这样的版本在 Item 45 中描述。

Things to Remember

拷贝一个 RAII object 必须拷贝它所管理的资源,所以资源的拷贝行为决定了 RAII object 的拷贝行为。

通常的 RAII class 的拷贝行为是 disallowing copying(不允许拷贝)和 performing reference counting(实行引用计数),但是其它行为也是有可能的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐