标签:拷贝 before 深拷贝 raw 执行 raii 解释 string类 默认
Item 13中介绍了 “资源获取之时也是初始化之时(RAII)”的概念,这个概念被当作资源管理类的“脊柱“,也描述了auto_ptr和tr1::shared_ptr是如何用堆资源来表现这个概念的。然而并不是所有资源都是在堆上创建的,对于这种资源,像auto_ptr和tr1::shared_ptr这样的智能指针就不适合当作资源句柄(handle)来使用了。你会发现你时不时的就会需要创建自己的资源管理类。
举个例子,假设你正在使用C API来操纵Mutex类型的互斥信号量对象,来为函数提供lock和unlock:
1 void lock(Mutex *pm); // lock mutex pointed to by pm 2 3 void unlock(Mutex *pm); // unlock the mutex
为了确保你不会忘记unlock一个已经加过锁的Mutex,你需要创建一个类来管理锁。这样一个类的基本结构已经由RAII准则表述过了,也就是资源会在执行构造的时候获取到,在执行析构的时候释放掉:
1 class Lock { 2 3 public: 4 5 explicit Lock(Mutex *pm) 6 7 : mutexPtr(pm) 8 9 { lock(mutexPtr); } // acquire resource 10 11 ~Lock() { unlock(mutexPtr); } // release resource 12 13 private: 14 15 Mutex *mutexPtr; 16 17 };
客户端以传统的RAII方式来使用锁:
1 Mutex m; // define the mutex you need to use 2 3 ... 4 5 { // create block to define critical section 6 7 Lock ml(&m); // lock the mutex 8 9 ... // perform critical section operations 10 11 } // automatically unlock mutex at end 12 13 // of block
这很好,但如果一个锁对象被拷贝会发生什么呢?
1 Lock ml1(&m); // lock m 2 3 Lock ml2(ml1); // copy ml1 to ml2 — what should 4 5 // happen here?
上面是一个更加普通的问题,也是每个RAII类的作者必须面对的:当一个RAII对象被拷贝的时候应该发生什么呢?大多数情况下,你将会从下面的4种可能中选择一个:
1 class Lock: private Uncopyable { // prohibit copying — see 2 3 public: // Item 6 4 5 ... // as before 6 7 };
通常情况下,RAII类可以通过包含一个tr1::shared_ptr数据成员来实现引用计数的拷贝行为。举个例子,如果Lock想使用引用计数,它可以将mutexPtr的类型从Mutex*改为tr1::shared_ptr<Mutex>。不幸的是,tr1::shared_ptr的默认行为是当引用技术为0的时候会删除它所指向的资源,这不是我们想要的。当我们实现一个Mutex类时,我们只是想unlock,并不想删除它们。幸运的是,tr1::shared_ptr允许指定自己的删除器(”deleter”)---一个函数或者函数对象,引用计数为0的时候会自动调用这个对像。(auto_ptr中不存在这个功能,它总是会删除指针。)这个删除器是tr1::shared_ptr构造函数的第二个可选参数,所以代码会是下面这个样子:
1 class Lock { 2 3 public: 4 5 explicit Lock(Mutex *pm) // init shared_ptr with the Mutex 6 7 : mutexPtr(pm, unlock) // to point to and the unlock func 8 9 { // as the deleter† 10 11 lock(mutexPtr.get()); // see Item 15 for info on “get” 12 13 } 14 15 private: 16 17 std::tr1::shared_ptr<Mutex> mutexPtr; // use shared_ptr 18 19 }; // instead of raw pointer
注意在这个例子中,Lock类不再声明析构函数。因为没有必要了。Item 5 解释到一个类的析构函数(无论是编译器生成的还是用户定义的)会自动调用类中的非静态数据成员的析构函数。在这个例子中,非静态数据成员为mutexPtr。但是在mutex的引用计数为0的时候其的析构函数会自动调用tr1::shared_ptr的删除器—也即是unlock。(人们在看到类的源码的时候如果有一行注释来说明你没有忘记析构,你只是使用了编译器默认生成的析构函数,他们会很感激的。)
有一些标准string类型的实现中包含了指向堆内存的指针,组成string的字符会保存在这块内存中。当一个string对象被拷贝的时候,会同时拷贝指针和指针指向的内存。这样的string展示出来的是深拷贝。
拷贝函数可能由编译器生成,所以除非编译器生成版本能够做到你想要的(Item 5解释了默认版本的行为),否则你需要自己实现它们。一些情况下你可能想支持这些函数的一般版本。这些版本在Item 45进行描述。
读书笔记 effective c++ Item 14 对资源管理类的拷贝行为要谨慎
标签:拷贝 before 深拷贝 raw 执行 raii 解释 string类 默认
原文地址:http://www.cnblogs.com/harlanc/p/6414789.html