前言
最近发现 refl-cpp 库可以实现编译期计算反射读写的结果,比较好奇内部的实现。因此在浅略地学习内部实现后,笔者实现了一个简单反射库,主要目的是学习 C++ 模板编程。
这个库支持以下功能:
Reflect::get(...)
反射读结构体成员属性Reflect::set(...)
反射写结构体成员属性forEach<T>(...)
遍历结构体成员
完整的代码放在了 这里,以下是使用的例子。
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
struct Point { float x; float y; float z; }; REFLECT_BEGIN(Point) REFLECT_FIELD(x) REFLECT_FIELD(y) REFLECT_FIELD(z) REFLECT_END void example() { Point pt{.x = 1, .y = 2, .z = 3}; std::cout << "get(x):" << Reflect::get(pt, TypeInfo<Point>::x()) << std::endl; Reflect::set(pt, TypeInfo<Point>::y(), 4); std::cout << "after set y=4:" << pt.y << std::endl; std::cout << "foreach:" << std::endl; Reflect::forEach<Point>([&](auto &&member) { std::cout << member.name.data() << ":" << Reflect::get(pt, member) << std::endl; }); // get(x):1 // after set y=4:4 // foreach: // x:1 // y:4 // z:3 } int main() { example(); }
编译期计算的字符串 ConstStr
在元信息中需要存储成员属性的名称,需要使用可以编译期进行表达、字符串连接、字符串判等的结构。以下定义了 ConstStr
,其通过字符串常量初始化。
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
template <int SIZE> struct ConstStr { static constexpr int LEN = SIZE - 1; constexpr explicit ConstStr() : _s{} {} // "const char (&s)[SIZE]" 表示 "const char s[SIZE]" 的引用 constexpr explicit ConstStr(const char (&s)[SIZE]) : ConstStr(s, std::make_index_sequence<SIZE>()) {} private: template <size_t... Idx> constexpr explicit ConstStr(const char (&s)[SIZE], std::index_sequence<Idx...>) : _s{s[Idx]...} {} char _s[SIZE]; };
以下补充一些辅助方法:
C++
1 2 3 4 5
template <int SIZE> struct ConstStr { constexpr size_t size() const { return LEN; } constexpr inline auto data() const { return _s; } constexpr inline auto data() { return _s; } };
再实现字符串的相连与判等
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
template <int N, int M> constexpr bool operator==(const ConstStr<N> &a, const ConstStr<M> &b) noexcept { if constexpr (N != M) { return false; } else { // 最后是 \0,可以跳过对比 for (size_t i = 0; i < M - 1; i++) { if (a.data()[i] != b.data()[i]) { return false; } } return true; } } template <int N, int M> constexpr ConstStr<N + M> operator+(const ConstStr<N> &a, const ConstStr<M> &b) noexcept { auto s = ConstStr<N + M>(); for (auto i = 0; i < N; i++) { s.data()[i] = a.data()[i]; } for (auto j = 0; j < M; j++) { s.data()[N + j] = b.data()[j]; } return s; }
元信息存储
假定需要支持结构体 Point { float x; float y; float z; }
的反射读写能力,以属性 x
为例,使用 traits 技巧记录以下信息:
- 成员类型
- 成员名,使用
ConstStr
表达 - 成员指针,当有数据时可获取成员值
对于整一个结构,需要记录:
- 成员数量
以下使用 Member<Idx>
是为了后续能够使用宏生成,且能够遍历。
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
template <typename T> struct TypeInfo; template <> struct TypeInfo<Point> { typedef Point ClassType; template <size_t Idx> struct Member {}; template <> struct Member<0> { public: // 成员类型 typedef decltype(ClassType::x) MemberType; // 成员名 static constexpr auto name = ConstStr("x"); // 成员指针 static constexpr auto pointer = &ClassType::x; }; // 便于构造属性 x 的元信息 static constexpr Member<0> x() { return {}; } // 成员数量 static constexpr size_t memberCount = 1; };
反射读写
有了元信息的读取结构就可以实现反射读写方法。
C++
1 2 3 4 5 6 7 8 9 10 11 12
// 读方法,如 get(pt, TypeInfo<Point>::x()) template <typename C, typename M> constexpr static auto get(const C &obj, M) { constexpr auto pointer = M::pointer; return obj.*pointer; } // 写方法,如 set(pt, TypeInfo<Point>::x(), 1.f) template <typename C, typename M> constexpr static void set(C &obj, M, const typename M::MemberType &val) { constexpr auto pointer = M::pointer; obj.*pointer = val; }
forEach
定义空的结构体 TypeList
用于承载元信息列表。
C++
1
template <typename... T> struct TypeList {};
由于成员元信息使用 Member<Idx>
记录,同时已知成员数量 memberCount
,因此可以使用 std::index_sequence
获取成员列表。
C++
1 2 3 4 5 6 7 8 9
template <typename C> constexpr static auto getMembers() { constexpr auto seq = std::make_index_sequence<TypeInfo<C>::memberCount>(); return getMembers<C>(seq); } template <typename C, size_t... Idx> constexpr static TypeList<Member<C, Idx>...> getMembers(std::index_sequence<Idx...>) { return {}; }
这样就可以通过已获取的成员列表实现遍历方法。
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
template <typename C, typename F> constexpr static void forEach(F &&f) { forEach(getMembers<C>(), std::forward<F>(f)); } template <typename F, typename... Ts> constexpr static void forEach(TypeList<Ts...>, F &&f) { forEach(TypeList<Ts...>{}, std::make_index_sequence<sizeof...(Ts)>(), std::forward<F>(f)); } template <typename F, typename... Ts, typename T, size_t... Idx, size_t I> constexpr static void forEach(TypeList<T, Ts...>, std::index_sequence<I, Idx...>, F &&f) { // 生成 T 类型对象并调用 callback f(T{}); forEach(TypeList<Ts...>{}, std::index_sequence<Idx...>{}, std::forward<F>(f)); } template <typename F> constexpr static void forEach(TypeList<>, std::index_sequence<>, F &&) {}
以下是对 Point
使用 forEach
的例子。
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
// 遍历全部属性 void example_iterateAllProps() { Point pt{.x = 1, .y = 2, .z = 3}; Reflect::forEach<Point>([&](auto member) { std::cout << member.name.data() << ":" << Reflect::get(pt, member) << std::endl; }); // Output: // x:1 // y:2 // z:3 } // 遍历部分属性 void example_iterateSomeProps() { typedef TypeInfo<Point> T; // 将需要的属性组成 TypeList constexpr TypeList<decltype(T::x()), decltype(T::z())> props; Point pt{.x = 1, .y = 2, .z = 3}; Reflect::forEach(props, [&](auto member) { std::cout << member.name.data() << ":" << Reflect::get(pt, member) << std::endl; }); // Output: // x:1 // z:3 }
宏生成元信息
将之前手写 TypeInfo
与 Member
的地方转为宏生成以方便使用。宏的实现如下:
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
#define REFLECT_BEGIN(Type) \ template <> struct TypeInfo<Type> { \ typedef Type ClassType; \ template <size_t Idx> struct Member {}; \ static constexpr size_t fieldOffset = __COUNTER__; #define REFLECT_FIELD(Field) \ static constexpr size_t MEMBER_##Field##_OFFSET = __COUNTER__; \ template <> struct Member<MEMBER_##Field##_OFFSET - fieldOffset - 1> { \ public: \ typedef decltype(ClassType::Field) MemberType; \ static constexpr auto name = ConstStr(#Field); \ static constexpr auto pointer = &ClassType::Field; \ }; \ static constexpr Member<MEMBER_##Field##_OFFSET - fieldOffset - 1> Field() { \ return {}; \ } #define REFLECT_END \ static constexpr size_t memberCount = __COUNTER__ - fieldOffset - 1; \ };
其中的关键是 Member<Idx>
与 memberCount
如何生成的问题。这里用到了内置宏 __COUNTER__
,其初值为 0,每预编译一次会自增 1。那么在开始时记录下其值,最后再记录一次就能够算出成员数量,每次定义成员元信息记录其值能够算出自增值。
对 Point
使用宏生成元数据信息:
C++
1 2 3 4 5
REFLECT_BEGIN(Point) REFLECT_FIELD(x) REFLECT_FIELD(y) REFLECT_FIELD(z) REFLECT_END