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.
CodePudding user response:
Make a second function which returns the size of your class:
extern size_t sizeofMyClass();
size_t sizeofMyClass() { return sizeof(MyClass); }
If you want it at compile time, than constexpr
it.
CodePudding user response:
You can declare myClassContainer
in the "someFile" translation unit but actually define it in the "myClass" translation unit. This can be made easier with a common header file:
// globaldefs.h
#include <cstdint>
extern std::uint8_t myClassContainer[];
#ifdef MY_CLASS_DEFINED
alignas(MyClass) std::uint8_t myClassContainer[sizeof(MyClass)];
#endif
// MyClass.h
class MyClass : public Interface {
// lots of members
};
#define MY_CLASS_DEFINED
#include "globaldefs.h"
// somefile.cpp
#include "globaldefs.h"
extern Interface* createMyClassAt(uint8_t* location);
int main() {
createMyClassAt(myClassContainer);
// more stuff
}
That way, the translation unit with somefile.cpp
only sees std::uint8_t myClassContainer[];
, and no size is needed.
CodePudding user response:
This is a second attempt to answer the question.
You cannot do what you want. The only reason to hide the implementation (PIMPL) is to allow you to compile the module completely independently. You cannot do this, whilst at the same time as injecting a dependency. What you ask for is self-contradictory.
Either declare the class in the header, so that the dependency is declared, or put the array in the module that contains the class. If its a static array, why does it matter which module it is declared in.
In other words, use a singleton, accessible via a free-function
CodePudding user response:
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
}