具有访客模式和静态内存分配的持久配置

Persistent config with visitor pattern and static memory allocation

提问人:klonyyy 提问时间:9/23/2023 最后编辑:klonyyy 更新时间:9/23/2023 访问量:63

问:

我正在为嵌入式项目准备一个持久配置模块。我想使用通过访客模式实现的序列化:

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);
    }
};

当涉及到存储模块时,我想像这样注册每个结构:

PersistentConfig conf{};

conf.register(&softwareInfo, "swinfo"); 
conf.register(&foo, "foo");

然后执行写入或读取函数,该函数获取所有已注册的对象,并序列化或反序列化它们以执行写入或读取操作。同时写入它们更方便,因为我无法写入事先未擦除的 FLASH,并且最小擦除大小为单页 (2048B)。

由于它是一个嵌入式项目,我想避免动态分配,如果没有它,我在实现类时会遇到麻烦。我想象它包含一个指向某些指针的数组,我可以将结构注册到这些指针:PersistentConfig persistentEntry

struct persistentEntry
{
    EntryBase* object;
    std::string_view name;
    uint32_t size;
    uint32_t datacrc32;
};

std::array<persistentEntry, maxEntries> entries;

我想创建一个 EntryBase 类,然后创建一个模板化的派生类,该类可以保存指向任何类型的已注册对象的指针:

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;
};

但是我需要在register事件期间在堆上创建Entry:

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;
}

我还想到只保留指向函数的指针,但由于它也模板化了,因此它无法正常工作。pack

有没有办法在不需要动态分配对象的情况下保存已注册的结构指针?或者我应该坚持使用此解决方案,只创建一个可以安全分配的固定内存池?Entry

C++ 序列化 嵌入式 访客模式 静态分配

评论

0赞 Swift - Friday Pie 9/23/2023
你看到了吗?有一个虚拟的消息库(在您的例子中)有方法可以复制底层数据,而无需知道调用站点 - , , 等。但是底层实现确实有一些由库维护的池。不是在注册表容器站点,而是在“消息”站点。根据空间和性能要求,您实际上可以使用 protobuf 来序列化您的数据google::protobufEntryMessageLite::NewMessageLite::CheckTypeAndMergeFrom
1赞 πάντα ῥεῖ 9/23/2023
我大致记得有一次解决了选择当前 Flash 页面的问题,该页面始终包含完整的配置,将其读入模型,并将其保存到下一个空闲页面,将其标记为当前活动的页面。也许这会有所帮助。
0赞 Swift - Friday Pie 9/23/2023
@πάνταῥεῖ Flash似乎更可取,因为无论你写了多少,它仍然一次按固定块写入。否则,添加新数据可能会导致额外的重写。
1赞 πάντα ῥεῖ 9/23/2023
@swift据我所知,我们很快就使用带有闪存文件系统的微型 linux 克服了这些低级媒体问题。
0赞 Swift - Friday Pie 9/23/2023
@πάνταῥεῖ如果这是负担得起的,是的。如果它是基于PLD的DSP计算机,那可能是一个问题

答:

0赞 Jarod42 9/23/2023 #1

有没有办法保存已注册的结构指针,而无需动态分配 Entry 对象?

您仍然可以放置 new,特别是因为所有派生类只有一个指针作为成员。

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();
    }

};

另一种方法是模仿 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); }
};