Best way to use RAII conditionally
I have a nice resource managing class. For concreteness, lets have it be a File class for managing a FILE* (handling the open and close operations)
What is the usual approach when there are cases where the resource doesn't need to be managed by me, and is someone else's responsibility?
For ilustra开发者_如何学JAVAtive purposes, I currently have something like this:
int main(int argc, char** argv)
{
File my_file(argv[1]); //I unconditionaly obtain the resource
//...
return 0; //and unconditionally relinquish with the destructor
}
And want something like
int main()
{
if(argc <= 1){
//use stdin that is already available
}else{
//obtain a file from argv[1]
}
//...
if(argc <= 1){
//nothing to do
}else{
//close the file we obtained
}
}
(but less ugly, more robust, etc...)
boost::shared_ptr
allows you to pass in a custom destructor. If you're wrapping an externally-managed pointer, you can pass a no-op:
namespace {
template<typename T>
void noop_destruct(T *) throw() { }
}
template<typename T>
boost::shared_ptr<T> make_dummy_shared_ptr(T *p) {
return boost::shared_ptr<T>(p, noop_destruct);
}
Now when you need a true RAII object, use a normal boost::shared_ptr
, and when you need a fake one, use an adapter like this - it'll act exactly like a normal pointer.
Your RAII class is already keeping enough state to know when to destroy the resource it is controlling. It can also contain a flag that tells it if the resource should be destroyed, or you can use a special value on the counter to indicate that the resource is controlled outside the class.
Then all you need is a way to control the state when you obtain the resource. You can have two different constructors for example, or a parameter on the constructor with a default value. You could have an Attach
method that attaches an existing resource. It's entirely up to you.
You can push the logic of whether or not to use the resource inside your resource managing class. Then it isn't conditional anymore. Just do
int main(int argc, char** argv)
{
File my_file(argc > 1 ? argv[1]: NULL); //If NULL, File will point to stdin
//...
return 0; //File's destructor will run, relinquishing resources if necessary.
}
Most common patterns do not allow for this. However, you can allow custom allocator plugins, which the Standard has for it's containers, that will allow for these semantics. This is a brief sample-
class Allocator {
File* Allocate(...) {
return fopen(...);
}
};
class MyStdinAllocator {
File* Allocate(...) {
return ...;
}
};
template<typename MyAllocator = Allocator> class File {
File* ptr;
Allocator alloc;
File(..., const Allocator& allocref)
: alloc(allocref) {
ptr = alloc.Allocate(...);
}
};
精彩评论