Freddie Chopin
Freddie Chopin

Reputation: 8860

Is it possible to invert order of destruction?

I have a base class that implements many basic functionalities and it needs some "storage" (memory block) that must be provided by a class that inherits it (or the user).

class Base
{
public:

    Base(void* storage, size_t storageSize) :
        storage_{storage},
        storageSize_{storageSize}
    {
        // do something with the storage...
    }

    ~Base()
    {
        // do something with the storage...
    }

    // member functions

private:

    void* storage_;
    size_t storageSize_;
};

The important note here is that this memory block is used in constructor and destructor.

This works very well when the child class uses static storage:

template<size_t Size>
class StaticObject : public Base
{
public:

    StaticObject() :
        Base{&storage, Size}
    {

    }

private:

    typename std::aligned_storage<Size>::type staticStorage_;
};

I know that the storage is used before it is constructed (it is "constructed" after the constructor of Base finishes) and after it is destructed (it is "destructed" before the destructor of Base starts running), but for the trivial std::aligned_storage<...>::type this makes no difference.

However this idea fails completely when I want to use it with dynamically allocated storage:

class DynamicObject : public Base
{
public:

    DynamicObject(size_t size) :
        DynamicObject{std::unique_ptr<uint8_t>{new uint8_t[size]}, size}
    {

    }

private:

    DynamicObject(std::unique_ptr<uint8_t>&& dynamicStorage, size_t size) :
        Base{dynamicStorage.get(), size},
        dynamicStorage_{std::move(dynamicStorage)}
    {

    }

    std::unique_ptr<uint8_t> dynamicStorage_;
};

As you see with the delegating constructor I managed to create (allocate) the storage before it will be used in the constructor of Base - "inverting" the order of construction a little bit. This particular phase also works fine. The problem is the destructor, as I really cannot think of any solution to my problem - in the code above, the dynamically allocated storage would be deallocated before the destructor of Base starts running (and using this memory block)...

For now I had to solve this differently - instead of inheriting from Base, the DynamicObject class contains the unique_ptr and the object of Base as member variables - this way I control the order of construction/destruction, but there are also some negative sides:

I have thought about using multiple inheritance, so that DynamicObject would inherit from two bases - one that would provide storage (private inheritance) and from Base (to inherit the functionality) - this way I would also get the right order of construction/destruction, but at the price of using the "evil" multiple inheritance...

Please note that the example above is just a simplification. The real use cases are objects like threads and message queues for the RTOS that I'm writing ( https://github.com/DISTORTEC/distortos ) - see the Dynamic*.hpp and Static*.hpp objects for real examples - https://github.com/DISTORTEC/distortos/tree/master/include/distortos

Is there any clever trick that I could use to somehow invert the order of destruction? Something like the use of delegating constructor of DynamicObject above? Maybe there is a better way to achieve the same result?

Upvotes: 4

Views: 267

Answers (3)

Ben Voigt
Ben Voigt

Reputation: 283614

You need to use a type-erased deleter, in the same fashion that shared_ptr does (but not the way unique_ptr does, where it becomes part of the type).

class Base
{
public:
    typedef void (*StorageDeleter)(void*);

    Base(void* storage, size_t storageSize, StorageDeleter deleter = nullptr) :
        storage_{storage},
        storageSize_{storageSize},
        deleter_{deleter}
    {
        // do something with the storage...
    }

    virtual ~Base()
    {
        // do something with the storage...
        if (deleter_) deleter_(storage_);
    }

    // member functions

private:
    void* storage_;
    size_t storageSize_;
    StorageDeleter deleter_;
};

/* no changes to this one */
template<size_t Size>
class StaticObject;

class DynamicObject : public Base
{
    static void array_deleter(void* p) { uint8_t* pExact = (uint8_t*)p; delete [] pExact; }
public:
    DynamicObject(size_t size) :
        DynamicObject{std::unique_ptr<uint8_t[]>{new uint8_t[size]}, size}
    {

    }

private:
    DynamicObject(std::unique_ptr<uint8_t[]>&& dynamicStorage, size_t size) :
        Base{dynamicStorage.get(), size, &DynamicObject::array_deleter},
    {
        dynamicStorage.release();
    }
};

Note that the deleter is a static member function -- it doesn't require a live derived instance of DynamicObject to operate correctly.

I also fixed the your usage of std::unique_ptr so that the array deallocator gets used in case of an exception thrown during construction of Base (after construction, the deleter function is responsible).

Now, consider that (pointer+deleter) already exists, in the form of std::unique_ptr<T, Deleter>. So you can do:

class Base
{
    typedef void (*StorageDeleter)(void*);
    typedef std::unique_ptr<void, StorageDeleter> AutofreePtr;

public:
    Base(AutofreePtr&& storage, size_t storageSize) :
        storage_{std::move(storage)},
        storageSize_{storageSize}
    {
        // do something with the storage...
    }

    virtual ~Base()
    {
        // do something with the storage...
    }

    // member functions

private:
    AutofreePtr storage_;
    size_t storageSize_;
};

template<size_t Size>
class StaticObject : public Base
{
    static void no_delete(void*) {}
public:

    StaticObject() :
        Base{{&storage, &StaticObject::no_delete}, Size}
    {

    }

private:

    typename std::aligned_storage<Size>::type staticStorage_;
};

class DynamicObject : public Base
{
    static void array_deleter(void* p) { uint8_t* pExact = (uint8_t*)p; delete [] pExact; }
public:

    DynamicObject(size_t size) :
        DynamicObject{{new uint8_t[size], &DynamicObject::array_deleter}, size}
    {

    }
};

Upvotes: 1

user1084944
user1084944

Reputation:

It sounds like what you really want is to reverse the hierarchy; a Base should have some storage, rather than, say, a StaticObject having a Base. This can, for example, be implemented with generics

template< typename Storage >
class Base
{
    Storage storage; // Storage could be a private base class too
public:
    // Fix: use perfect forwarding
    template< typename T... >
    Base(T ...args):Storage(args...) { /* More initialization */ }

    ~Base() {
        // Still safe to use storage!
    }

    void set_all_storate_to_zero()
    {
        memset(storage.ptr(), 0, storage.size());
    }
};

Upvotes: 1

Neil Kirk
Neil Kirk

Reputation: 21763

This avoids the problem of using a storage object for work before it may have been fully initialized.

class Base
{
public:

    Base(void* storage, size_t storageSize) :
        storage_{storage},
        storageSize_{storageSize}
    {
    }

    virtual ~Base()
    {
    }

private:
    void* storage_;
    size_t storageSize_;
};

class Worker
{
    Worker(Base* storage)
        : storage(storage)
    {
        // do something with the storage
    }

    ~Worker()
    {
        // do something with the storage
    }

    Base* storage;
};

Base* storage = new FancyStorage;
Worker w(storage);
delete storage;

I avoided smart pointers to keep it simple as I don't know how you want your storage objects to be owned.

Upvotes: 1

Related Questions