大学IT网 - 最懂大学生的IT学习网站! QQ资料交流群:367606806
当前位置:大学IT网 > C++技巧 > C++技巧:谨慎考虑资源管理类的拷贝行为

C++技巧:谨慎考虑资源管理类的拷贝行为

关键词:C拷贝行为  阅读(565) 赞(15)

[摘要]本文主要是对C++技巧:谨慎考虑资源管理类的拷贝行为的讲解,希望对您学习C++有所帮助!

在上一篇文章中引见了作为资源管理类支柱的 Resource Acquisition Is Initialization (RAII) 准绳,并描画了 auto_ptr 和 tr1::shared_ptr 在基于堆的资源上运用这一准绳的表现。并非一切的资源都是基于堆的,但是,关于这样的资源,像 auto_ptr 和 tr1::shared_ptr 这样的智能指针通常就不像 resource handlers(资源管理者)那样适合。在这种情况下,有时,你可以要根据你自己的需求去创建你自己的资源管理类。

例如,假定你运用 C API 提供的 lock 和 unlock 函数去操纵 Mutex 类型的互斥体对象:

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

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

为了确保你从不会遗忘解锁一个被你加了锁的 Mutex,你希望创建一个类来管理锁。RAII 准绳规则了这样一个类的基本结构,经过构造函数获取资源并经过析构函数释放它:

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 对象被拷贝应该发作什么?

Lock ml1(&m); // lock m

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

这是一个更普通效果的特定实例,每一个 RAII 类的作者都要面临这样的效果:当一个 RAII 对象被拷贝的时分应该发作什么?大多数情况下,你可以从下面各种可以性中挑选一个:

制止拷贝。在很多情况下,允许 RAII 被拷贝是没有意义的。这关于像 Lock 这样类很可以是正确的,由于同步的基本要素的“副本”很少有什么意义。当拷贝对一个 RAII 类没有什么意义的时分,你应该制止它。Item 6 解释了如何做到这一点。声明拷贝操作为私有。关于 Lock,看起来也许像这样:

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

对底层的资源引用计数。有时人们需求的是坚持一个资源直到最后一个运用它的对象被销毁。在这种情况下,拷贝一个 RAII 对象应该添加引用这一资源的对象的数目。这也就是运用 tr1::shared_ptr 时“拷贝”的含义。

通常,RAII 类只需求包括一个 tr1::shared_ptr 数据成员就可以完成引用计数的拷贝行为。例如,假定 Lock 要运用引用计数,他可以要将 mutexPtr 的类型从 Mutex* 改动为 tr1::shared_ptr<Mutex>。不幸的是,tr1::shared_ptr 的缺省行为是当它所指向的东西的引用计数变为 0 的时分将它删除,但这不是我们要的。当我们运用 Mutex 终了后,我们想要将它解锁,而不是将它删除。

幸运的是,tr1::shared_ptr 允许一个 "deleter" 规范——当引用计数变为 0 时调用的一个函数或许函数对象。(这一功用是 auto_ptr 所没有的,auto_ptr 总是删除它的指针。)deleter 是 tr1::shared_ptr 的构造函数的可选的第二个参数,所以,代码看起来就像这样:

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 类是如何不再声明一个析构函数的。那是由于它不再需求。Item 5 解释了一个类的析构函数(无论它是编译器生成还是用户定义)会自动调用这个类的非静态(non-static)数据成员的析构函数。在本例中,就是 mutexPtr。但是,当互斥体的引用计数变为 0 时,mutexPtr 的析构函数会自动调用的是 tr1::shared_ptr 的 deleter ——在此就是 unlock。(看过这个类的源代码的人多半看法到需求添加一条注释标明你并非遗忘了析构,而只是依赖编译器生成的缺省行为。)

拷贝底层的资源。有时就像你所希望的你可以拥有一个资源的多个副本,独一的前提是你需求一个资源管理类确保当你运用完它之后,每一副本都会被释放。在这种情况下,拷贝一个资源管理对象也要同时拷贝被它隐藏的资源。也就是说,拷贝一个资源管理类需求完成一次“深层拷贝”。

某些标准 string 类型的完成是由堆内存的指针组成,堆内存中存储着组成那个 string 的字符。这样的字符串对象包括指向堆内存的指针。当一个 string 对象被拷贝,这个副本应该由那个指针和它所指向的内存组成。这样的 strings 表现为深层拷贝。

传递底层资源的一切权。在某些特殊场所,你可以希望确保只需一个 RAII 对象引用一个未加工的资源,而当这个 RAII 对象被拷贝的时分,资源的一切权从被拷贝的对象传递到拷贝对象。就像上一篇文章所说明的,这就是运用 auto_ptr 时“拷贝”的含义。

拷贝函数(copying functions)(拷贝构造函数和拷贝赋值运算符)可以是由编译器生成的,所以除非编译器生成的版本所做的事正是你所要的,你应该自己编写它们。在某些情况下,你也要支持这些函数的泛型化版本。

Things to Remember

·拷贝一个 RAII 对象必需拷贝它所管理的资源,所以资源的拷贝行为决议了 RAII 对象的拷贝行为。

·普通的 RAII 类的拷贝行为不接受拷贝和中止引用计数,但是其它行为是有可以的。



相关评论