2

我需要开发一个管理器,它接受 astruct并将其成员存储到数据库行,并struct再次用数据库行中的值填充 a。相同struct的对象存储在同一个表中。这是我们的例子strcut

struct Person
{
    std::string Name;
    int Age;

    // more members that shouldn't be stored or loaded
    // ...
};

struct我的目标是用一行将 s加载并存储到他们的表中。

// example for storing
unsigned int id = 42;          // not hardcoded of course
Person paul = { "Paul", 8 };   // instantiate the struct
Data->Save<Person>(id, *paul); // table name is derived from template argument

// example for fetching
unsigned int id = 42;
Person paul;
Data->Load<Person>(id, *paul);

所有structs 都可以通过继承强制实现序列化和反序列化功能,或者我可以使用 Matthieu M. 在答案中建议的基于宏的反射。但我无法对struct存储的 s 进行更改。所以我决定那些structs必须先注册,提供它们的序列化和反序列化功能。(我的意思是从实例转换为struct数据库行对象,反之亦然。)

class Data
{
    typedef std::function<row(T*) Serialization;
    typedef std::function<void(row, T*) Deserialization;
    typedef std::pair<Serialization, Deserialization> Registereds;
    std::unordered_map<std::type_index, Registered> List;
public:
    template <typename T>
    Register<T>(std::function<row(T*) Serialize,
                std::function<void(row, T*) Deserialize)
    {
        // create a table based on the template argument
        std::string name = typeid(T).name();
        // ...

        // add conversion functions to the list
        auto index = std::type_index(typeid(T));
        List.insert(index, std::make_pair(Serialize, Deserialize));
    }

    // load and save functions shown above go here
    // ...
};

您可能已经注意到row我在此示例中使用的数据类型。这正是我的问题!对于每一struct行,都包含不同类型的不同字段。那么我可以使用什么数据结构来保存一行?

我虽然关于使用某种std::tuple. 例如,如果一个元组可以有字符串键,那是有可能的。然后,我必须完全模板化所有内容以使用不同struct的 s。它可能看起来类似于下面的这个草稿。但是我必须知道如何在不使用指针的情况下将不同structs的转换函数存储在同一个中。Listvoid*

Data->Register<Person, std::string, int>(
    [](Person *Instance) -> std::tuple<std::string, int>{ /* ... */ }, 
    [](std::tuple<std::string, int> Row, Person *Instance) -> void{ /* ... */ }
);

如何表示数据库行对象?如果我的模板方法是一个好方法,我该如何模板化所有内容?

更新以响应马蒂厄 M. 的回答

您建议使用类型特征。这是我尝试过的。但我仍然不知道如何定义表示一行的类型。

性状

template <class T>
struct data_traits{ };

struct data_traits<Person>
{
    struct row{ int Age; std::string Name; }; // right to do here?
    row serialize(Person *Instance)
    {
        // ...
    }
    void deserialize(row Data, Person *Instance)
    {
        // ...
    }
};

数据管理员

class Data
{
    template <typename T>
    void Load(uint64_t Id, T *Instance)
    {
        auto traits = data_traits<T>;

        std::string query = "SELECT * FROM "
                          + typeid(T).name()
                          + " WHERE id = "
                          + std::to_string(Id);
        // fetch result from database
        // ...

        traits::row result;
        // convert SQL result into type dependent row type
        // but how to do that?
        // ...

        traits.deserialize(result, Instance);
    }
};
4

1 回答 1

1

注意:我删除了我之前的答案,因为它对问题的解释完全不同;如果有人希望看到它,我仍然将它(删除)作为参考。

您所要求的可以用具有反射的语言来完成。幸运的是,不需要完整的反射,C++ 仍然没有提供任何开箱即用的功能。

警告:我个人认为这种需求值得怀疑。要求在结构(及其字段名称)和数据库表(及其列名称)之间进行一对一的映射,在维护方面的成本可能会超过其价值。值得注意的问题是如何在更改数据库模式时保持向后/向前兼容性。/警告


添加反射的第一个标准方法是考虑BOOST_FUSION_ADAPT_STRUCT.

struct Person {
    struct Name: Field<std::string> { static char const* N() { return "name"; } }
    struct Age: Field<int> { static char const* N() { return "age"; } }

    Name name;
    Age age;
}; // struct Person

BOOST_FUSION_ADAPT_STRUCT(
    Person,
    (Name, name)
    (Age, age))

这是一个双重技巧:

  • 可以在运行时迭代 Fusion 序列
  • 每个属性都增加了一个名称

注意:同样的事情可以通过使用Boost.Fusion.Map序列来实现。

当然,这并不完美,因为它实际上需要您更改结构的定义。尽管可能很困难,但可能会提出一个等效的宏来进行名称增强,以避免不得不更改基本结构,并且如果您设法这样做,那么值得发布您自己的解决方案:)


另一种解决方案是实际采用更全面的基于反射的方法。@Paul在此处添加了基于 C99 可变参数宏的反射解决方案。

如果您喜欢宏,我会注意到它可能会通过 Boost.Preprocessor 进行调整以使用它的序列(会添加更多括号但是标准的)。

但是,这再次需要修改结构的声明。


就个人而言,我希望看到第一个解决方案得到增强以适应第 3 方结构。另一方面,考虑到对象模型和数据库模式之间引入的强耦合,最好已经将自己限制在自己的类型中。

于 2013-08-18T15:11:35.433 回答