I'm preparing a persistent config module for an embedded project. I want to use serialization implemented using the visitor pattern:
struct softwareInfo
{
uint32_t version;
std::array<uint8_t, 40> commitHash;
uint32_t buildDate;
template <class T>
void pack(T& archive)
{
archive.process(version);
archive.process(commitHash);
archive.process(buildDate);
}
};
When it comes to the storage module I'd like to register each structure like so:
PersistentConfig conf{};
conf.register(&softwareInfo, "swinfo");
conf.register(&foo, "foo");
and then perform either a write or read function that takes all registered objects and serializes or deserializes them either for write or read operations. Writing them all at the same time is more convenient as I cannot write to FLASH that had not been erased beforehand and a minimum erase size is a single page (2048B).
Since it's an embedded project I'd like to avoid dynamic allocations and I have troubles implementing the PersistentConfig
class without it. I imagine it holding an array of pointers to some persistentEntry
, to which I can register the structs:
struct persistentEntry
{
EntryBase* object;
std::string_view name;
uint32_t size;
uint32_t datacrc32;
};
std::array<persistentEntry, maxEntries> entries;
I thought of creating an EntryBase class and then a templated derived class which could hold the pointer to any type of registered objects:
class EntryBase
{
public:
virtual ~EntryBase() {}
virtual bool pack(serializer::Packer& packer) = 0;
virtual bool pack(serializer::Unpacker& unpacker) = 0;
};
template <typename T>
class Entry : public EntryBase
{
public:
explicit Entry(T* item) : item(item)
{
}
bool pack(serializer::Packer& packer) override
{
return item->pack(packer);
}
bool pack(serializer::Unpacker& unpacker) override
{
return item->pack(unpacker);
}
T* const item;
};
but then I need to create Entry on heap during the register event:
template <class PackableObject>
bool registerEntry(PackableObject* obj, const std::string_view& name)
{
entries.at(numberOfEntries).object = new Entry<PackableObject>(obj); //I'd like to avoid that
entries.at(numberOfEntries).name = name;
entries.at(numberOfEntries).hashName = Checksum::crc32(name.data(), name.size());
return ++numberOfEntries > maxEntries;
}
I also thought of holding only the pointer to the pack
function, but since its also templated it won't work as well.
Is there a way to hold the registered structs pointers without the need to dynamically allocate the Entry
objects? Or should I stick with this solution and just create a fixed memory pool that I can safely allocate from?
Is there a way to hold the registered structs pointers without the need to dynamically allocate the Entry objects?
You still have the possibilities of placement new, especially as all derived class has only one pointer as member.
struct persistentEntry
{
aligned_storage_t<sizeof(Entry<void>), alignof(Entry<void>)> buffer;
EntryBase* object;
std::string_view name;
uint32_t size;
uint32_t datacrc32;
template <class PackableObject>
void init(PackableObject* obj, const std::string_view& name) {
static_assert(sizeof(Entry<PackableObject>) <= sizeof(buffer));
object = new (&buffer) Entry<PackableObject>(obj);
this->name = name;
datacrc32 = Checksum::crc32(name.data(), name.size());
}
void deinit() {
object.~EntryBase();
}
};
Another way is to mimic the vtable:
template <typename T>
bool packT(void* object, serializer::Packer& packer)
{
reurn reinterpret_cast<T*>(object)->pack(packer);
}
template <typename T>
bool unpackT(void* object, serializer::Unpacker& unpacker)
{
reurn reinterpret_cast<T*>(object)->pack(unpacker);
}
struct persistentEntry
{
void* object = nullptr;
bool (*pack)(void* object, serializer::Packer& packer) = nullptr;
bool (*unpack)(void* object, serializer::Unpacker& unpacker) = nullptr;
std::string_view name;
uint32_t size;
uint32_t datacrc32;
template <class PackableObject>
void init(PackableObject* obj, const std::string_view& name) {
object = obj;
pack = &packT<PackableObject>;
unpack = &unpackT<PackableObject>;
this->name = name;
datacrc32 = Checksum::crc32(name.data(), name.size());
}
bool call_pack(serializer::Packer& packer) { return pack(obj, packer); }
bool call_pack(serializer::Unpacker& unpacker) { return unpack(obj, unpacker); }
};