C++ 中没有反射。真的。但是如果编译器不能为你提供你需要的元数据,你可以自己提供。
让我们从创建一个属性结构开始:
template<typename Class, typename T>
struct PropertyImpl {
constexpr PropertyImpl(T Class::*aMember, const char* aName) : member{aMember}, name{aName} {}
using Type = T;
T Class::*member;
const char* name;
};
template<typename Class, typename T>
constexpr auto property(T Class::*member, const char* name) {
return PropertyImpl<Class, T>{member, name};
}
当然,您也可以使用property
一个 setter 和 getter 而不是指向成员的指针,并且可能只读取您想要序列化的计算值的属性。如果您使用 C++17,您可以进一步扩展它以创建一个适用于 lambda 的属性。
好的,现在我们有了编译时自省系统的构建块。
现在在您的班级Dog
中,添加您的元数据:
struct Dog {
std::string barkType;
std::string color;
int weight = 0;
bool operator==(const Dog& rhs) const {
return std::tie(barkType, color, weight) == std::tie(rhs.barkType, rhs.color, rhs.weight);
}
constexpr static auto properties = std::make_tuple(
property(&Dog::barkType, "barkType"),
property(&Dog::color, "color"),
property(&Dog::weight, "weight")
);
};
我们将需要在该列表上进行迭代。要迭代一个元组,有很多方法,但我更喜欢这样:
template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F&& f) {
using unpack_t = int[];
(void)unpack_t{(static_cast<void>(f(std::integral_constant<T, S>{})), 0)..., 0};
}
如果 C++17 折叠表达式在您的编译器中可用,则for_sequence
可以简化为:
template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F&& f) {
(static_cast<void>(f(std::integral_constant<T, S>{})), ...);
}
这将为整数序列中的每个常量调用一个函数。
如果此方法不起作用或给您的编译器带来麻烦,您可以随时使用数组扩展技巧。
现在您有了所需的元数据和工具,您可以遍历属性以反序列化:
// unserialize function
template<typename T>
T fromJson(const Json::Value& data) {
T object;
// We first get the number of properties
constexpr auto nbProperties = std::tuple_size<decltype(T::properties)>::value;
// We iterate on the index sequence of size `nbProperties`
for_sequence(std::make_index_sequence<nbProperties>{}, [&](auto i) {
// get the property
constexpr auto property = std::get<i>(T::properties);
// get the type of the property
using Type = typename decltype(property)::Type;
// set the value to the member
// you can also replace `asAny` by `fromJson` to recursively serialize
object.*(property.member) = Json::asAny<Type>(data[property.name]);
});
return object;
}
对于序列化:
template<typename T>
Json::Value toJson(const T& object) {
Json::Value data;
// We first get the number of properties
constexpr auto nbProperties = std::tuple_size<decltype(T::properties)>::value;
// We iterate on the index sequence of size `nbProperties`
for_sequence(std::make_index_sequence<nbProperties>{}, [&](auto i) {
// get the property
constexpr auto property = std::get<i>(T::properties);
// set the value to the member
data[property.name] = object.*(property.member);
});
return data;
}
如果你想要递归序列化和反序列化,你可以替换asAny
为fromJson
.
现在你可以像这样使用你的函数:
Dog dog;
dog.color = "green";
dog.barkType = "whaf";
dog.weight = 30;
Json::Value jsonDog = toJson(dog); // produces {"color":"green", "barkType":"whaf", "weight": 30}
auto dog2 = fromJson<Dog>(jsonDog);
std::cout << std::boolalpha << (dog == dog2) << std::endl; // pass the test, both dog are equal!
完毕!不需要运行时反射,只需一些 C++14 的优点!
这段代码可以从一些改进中受益,当然可以通过一些调整与 C++11 一起使用。
请注意,需要编写asAny
函数。它只是一个接受Json::Value
并调用正确as...
函数或另一个的函数fromJson
。
这是一个完整的工作示例,由该答案的各种代码片段组成。随意使用它。
如评论中所述,此代码不适用于 msvc。如果你想要一个兼容的代码,请参考这个问题:指向成员的指针:在 GCC 中工作,但在 VS2015 中不工作