[翻译] 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(实行引用计数),但是其它行为也是有可能的。
作者: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(实行引用计数),但是其它行为也是有可能的。
相关文章推荐
- [翻译] Effective C++, 3rd Edition, Item 15: 在 resource-managing classes(资源管理类)中提供对 raw resources(裸资源)的访问
- 读书笔记 effective c++ Item 14 对资源管理类的拷贝行为要谨慎
- Item 14:资源管理类要特别注意拷贝行为 Effective C++笔记
- [翻译] Effective C++, 3rd Edition, Item 39: 谨慎使用 private inheritance(私有继承)(下)
- [翻译] Effective C++, 3rd Edition, Item 12: 拷贝一个 object(对象)的所有 parts(构件)
- [翻译] Effective C++, 3rd Edition, Item 49: 了解 new-handler 的行为(上)
- [翻译] Effective C++, 3rd Edition, Item 40: 谨慎使用 multiple inheritance(多继承)
- C++箴言:谨慎考虑资源管理类的拷贝行为
- [翻译] Effective C++, 3rd Edition, Item 49: 了解 new-handler 的行为(下)
- [翻译] Effective C++, 3rd Edition, Item 39: 谨慎使用 private inheritance(私有继承)(上)
- [翻译] Effective C++, 3rd Edition, Item 25: 考虑支持一个 non-throwing swap(不抛异常的 swap)(下)
- [翻译] Effective C++, 3rd Edition, Item 35: 考虑可选的 virtual functions(虚拟函数)的替代方法(下)
- [翻译] Effective C++, 3rd Edition, Item 25: 考虑支持一个 non-throwing swap(不抛异常的 swap)(上)
- Effective C++ Item 14 在资源管理类中小心copying行为
- [翻译] Effective C++, 3rd Edition, Item 35: 考虑可选的 virtual functions(虚拟函数)的替代方法(上)
- Item 14:资源管理类要特别注意拷贝行为
- [翻译] Effective C++, 3rd Edition, Item 44: 从 templates(模板)中分离出 parameter-independent(参数无关)的代码(上)
- [翻译] Effective C++, 3rd Edition, Item 47: 为类型信息使用 traits classes(特征类)(上)
- [翻译] Effective C++, 3rd Edition, Item 48: 感受 template metaprogramming(模板元编程)
- [翻译] Effective C++, 3rd Edition, Item 30: 了解 inlining(内联化)的来龙去脉(上)