Issue
I have a bunch of complex classes in one translation unit which involves a bunch of header dependencies. In addition, the translation unit provides a factory function.
// MyClass.h
#include "Interface.h"
// lots of other includes
class MyClass : public Interface {
// lots of members
}:
// Creates an instance of MyClass using placement new
Interface* createMyClassAt(uint8_t* location);
In another translation unit I want to use multiple instances of different classes deriving from Interface
and I want to allocate them in static memory. It's a small embedded system without heap. I want to avoid the inclusion of MyClass.h
because some its dependencies are internal.
// somefile.cpp
#include "Interface.h"
extern Interface* createMyClassAt(uint8_t* location);
uint8_t myClassContainer[sizeofMyClass];
int main() {
createMyClassAt(myClassContainer);
// more stoff
}
My understanding is, that it is impossible to determine sizeofMyClass
without having the actual type information of MyClass
. No matter what I do. I cannot get this information across translation units.
How to achieve my goal then? Do I need to go via the build system and extract the sizes somehow from the object files and generate a header from that? That might be OK after all.
Edit 1: Some clarification:
- All those classes derived from Interface.h are defined in a prelinked, self-contained static library at the end.
- By "internal dependencies" I mean other header files and types that I don't want to leak to consumers of the library
- The consumers of the library may create multiple instances of various classes.
Solution
How to achieve my goal then?
Approuch 1: static assert and "gueess" sizes. There is no dependency between interface.h
and the class, but you have to manually update the header on each change (or, better, generate the header from the build system).
// interface.h
using Interface_storage = std::aligned_storage<20, 16>;
// ^^^^^^ - size and alignment
// They are _hard-coded_ here and _need_ to be _manually_ updated each time
// MyClass changes.
Interface* createMyClassAt(Interface_storage& location);
// interface.c
Interface* createMyClassAt(Interface_storage& location) {
// static assertion check
static_assert(sizeof(MyClass) == sizeof(Interface_storage) &&
alignof(MyClass) == alignof(Interface_storage),
" Go and fix the header file");
// use placement new on location
}
// main.c
int main() {
Interface_storage storage; // nice&clean
Interface *i = createMyClassAt(storage);
destroyMyClassAt(i, storage);
}
Approuch 2: Unix systems since the ages used file descriptors. A file descriptor is simple - it's just an index in an array of... somethings. It's trivial to implement, and you can hide everything behind a single integer value. That basically means, that you have to use dynamic memory, or you have to know in advance how many objects you need and allocate memory for all of them.
The below pseudocode implementation just returns the pointer to the interface, but it's very similar to returning an index in the array just like file descriptors.
// interface.h
Interface *new_MyClass();
destroy_MyClass(Interface *);
// interface.c
#define MAX 5
std::array<std::aligned_storage<sizeof(MyClass), alignof(MyClass)>, MAX> arr;
std::array<bool, MAX> used;
Interface *new_MyClass() {
// find the fist not used and return it.
for (size_t i = 0; i < used.size(); ++i) {
if (!used[i]) {
used[i] = true;
return new(arr[i]) MyClass();
}
}
return nullptr;
}
void destroy_MyClass(Interface *i) {
// update used array and destroy
}
Answered By - KamilCuk