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

Item 14: 谨慎考虑 resource-managing classes(资源管理类)中的拷贝行为

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

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

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

例如,假设你使用一个 C API 提供的 lockunlock 函数去操纵 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(实行引用计数),但是其它行为也是有可能的。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值