映射类的数据成员

Mapping data members of a class

本文关键字:数据成员 映射      更新时间:2023-10-16

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

假设我们有:

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的东西,这是一个例子。我只关心为每个成员存储一些额外的数据,而不与成员组成。

您可以使用BOOST_FUSION_DEFINE_STRUCT来定义可以使用for_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,每个类一个。每个成员都由其从其封闭类的开头开始的偏移量来标识。

通常,您将宏用于此类目的。他们可以生成您想要的任何类型的代码/包装器,让您拥有对数据的通常访问权限,还可以添加您想要/需要的东西。它不漂亮,但它有效。

有一些模板库可以在这里提供帮助,例如Boost.Fusion或Boost.Hana,但是,如果您不使用其高级功能(带有长编译价格标签(,您也可以在此处推出自己的模板库。

此外,如果您可以专注于特定的 GUI 框架,它们对这些东西有一些支持。例如,Qt有自己的"元对象"编译器。

你可以试试这个模板吗? 例如

template <typename T>
class GuiItem : public T {
map<MemberHandle, GuiElement*> guiHandles;
}
GuiItem<A> guiA;
guiA.x = 123;
guiA.y = "y";
guiA.guiHandles[handle] = element;

我不确定我是否了解其他要求,因此这种方式可能不适合您。