首页 > 解决方案 > 映射类的数据成员

问题描述

我正在尝试设计一种数据结构,它可以通过存储一些关于其成员的额外数据来增强/补充现有的数据结构。

假设我们有:

class A {
 int x;
 string y;
};

我们希望有一个与之关联的 GUI 组件,因此数据成员具有相应的 GUI 元素。我想将成员映射到各自的组件。就像是

class GuiA {
 int x;
 string y;
 map<MemberHandle, GuiElement*> guiHandles;
}

我没有任何限制,但我希望结果可以轻松转换为原始类型。

我知道,我可以引入一个模板,例如GuiElementMember保存原始数据和GuiElement指针,并将类成员交换为装饰的对应对象,所以它看起来像:

class GuiA {
 GuiElementMember<int> x;
 GuiElementMember<string> y;
}

但我想避免它,因为它完全改变了对数据成员的访问模式并使其膨胀。即它的结果是数据成员与指针交错,不容易去除。

理想情况下,可以写成GuiA的派生类A,或者写成 的组合A和附加的东西。

我正在考虑类可以生成地图的模板之类的东西。我可以为每个组件编写一个自定义类,但我认为没有一种简单的方法来映射数据成员,所以在客户端它看起来像getGuiMember(GuiA::x). 指向数据成员的指针包含成员原始类型。我认为不可能有像“类型擦除的成员指针”这样可以用作MemberHandle类型的东西。

我想到的唯一一件事是enum每个组件的自定义,它将枚举数据成员并用作地图(或本例中的向量)的键类型,但这似乎是大量的信息复制和维护。

是否有一些允许映射数据成员的技术?

只要接口简单,我并不真正关心实现的复杂性。我欢迎提升或模板魔术。我也不关心额外数据访问的性能,它是额外的东西,但普通类的使用不应该受到影响,因此引入无法优化的间接性不太受欢迎。

编辑:请不要依赖 GUI 的东西,这是一个例子。我只关心为每个成员存储一些额外的数据,而不是与成员一起组成。

标签: c++c++17data-members

解决方案


您可以使用BOOST_FUSION_DEFINE_STRUCTfor_each定义可以通过循环迭代的结构:

#include <boost/fusion/include/define_struct.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <unordered_map>
#include <string>
#include <cstdint>

BOOST_FUSION_DEFINE_STRUCT(
    (demo), employee,
    (std::string, name)
    (int, age)
    )

struct GuiElement;
GuiElement* createGuiElement(char const* name);

using Mapping = std::unordered_map<size_t, GuiElement*>;

template<class T>
Mapping create_mapping(T&& t) {
    Mapping mapping;
    boost::fusion::for_each(t, [&](auto& member) {
        auto offset = reinterpret_cast<uintptr_t>(&member) - reinterpret_cast<uintptr_t>(&t);
        mapping[offset];
    });
    return mapping;
}

template<class T, class M>
GuiElement*& get_mapping_element(Mapping& mapping, T const& t, M const& member) {
    auto offset = reinterpret_cast<uintptr_t>(&member) - reinterpret_cast<uintptr_t>(&t);
    auto found = mapping.find(offset);
    if(found == mapping.end())
        std::abort();
    return found->second;
}

int main() {
    auto employee_mapping = create_mapping(demo::employee{});

    demo::employee e1;
    get_mapping_element(employee_mapping, e1, e1.name) = createGuiElement("name");
    get_mapping_element(employee_mapping, e1, e1.age) = createGuiElement("age");
}

在代码中有一个Mapping, 每个类一个。每个成员都由其与其封闭类开头的偏移量来标识。


推荐阅读