Reputation: 8860
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:
Base
that should be exposed by the derived classesBase
, as I would like to use the objects via reference to the base classI 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
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
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
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