jeudi 21 avril 2022

How to design a RAII wrapper to be used as a base class?

I had the idea to use std::unique_ptr as a base class for another class that manages some kind of external resource.

Let's assume my program needs to manage resources provided by a C-like library via the following functions: R* lib_create_resource(A* allocator); and void lib_destroy_resource(R* resource, A* allocator);. I would like to create a class that manages this resource, so I thought that using std::unique_ptr as a base class would be a good idea. Here's a pseudo-implementation:

struct Resource: public std::unique_ptr<R, std::function<void(R*)>> {
    Resource(): 
        unique_ptr{
            lib_create_resource(&my_allocator),
            [this](R* r) { lib_destroy_resource(r, &my_allocator); }
        }
    { }

    /* Other functions that manipulate the resource */

private:
    A my_allocator;
};
  • Why am I using std::unique_ptr as a base class rather than as a non-static member? Because I would like to expose std::unique_ptr's methods, such as get(), reset(), operator bool(), etc., and this way I don't need to manually re-define each of them, especially when the library provides many kinds of different resources instead of just one, and I want to write a separate wrapper for each of them.
  • Why not use std::unique_ptr<R, std::function<void(R*)>> on its own then, without the Resource class? Because I would like to extend std::unique_ptr and provide additional methods, specific to this type of resource.

Now, the above pseudo-implementation has two major problems, which are the main point of my question.

  1. Since base classes are initialised before non-static members, my_allocator is passed to lib_create_resource() uninitialised. This isn't hard to fix, as I can just default-initialise the unique_ptr base and re-assign it in the constructor's body, but I think it's easy to forget about this.
  2. Similarly, during destruction, the non-static members are destroyed before the base classes. First, my_allocator will be destroyed, then ~unique_ptr() will be called, which in turn will call lib_destroy_resource() with my_allocator. But at that point, my_allocator no longer exists.

I haven't been able to come up with any solution for issue number 2. Is there a way to re-design this class so that lib_destroy_resource() doesn't access my_allocator outside its lifetime? Of course, one solution would be to manually call lib_destroy_resource() or std::unique_ptr::reset() at the appropriate times, but automatic resource management with RAII is considered good practice, especially when exceptions are involved. So is there a better way to accomplish this, possibly by implementing my own std::unique_ptr-like class?

Aucun commentaire:

Enregistrer un commentaire