C++反射机制详解(一) 动态反射

在实际的项目开发中,我们有时候需要进行一些操作比如说序列化。而如果我们对所有的内容都进行hardcode,显然这样的工作比较繁琐。因此,使用反射可以让我们在运行时获得类型信息,从而更方便的进行开发。在C++中原生不能提供反射机制,因此,通常需要我们手动去实现。

在C++中,常见的反射实现通常分为动态反射和静态反射。静态反射将类型信息在编译时进行导出,通常情况下使用parser在编译时期生成一些辅助代码,通过对类型、字段和函数的标记,生成相应的反射类型信息,如Unreal Engine、Qt等都是通过静态反射去实现反射的功能。动态反射是在运行时将类型信息记录下来,通常需要我们手动去register所需要的反射信息。

一种简单的反射实现

首先,我们可以从简单开始,考虑一个最基本的问题,我们如何通过一个类名的字符串创建出对应的类。我们可以通过一个Class工厂类,存储我们注册的类名和类的构造信息。在创建类的同时,我们实现一个类的回调构造函数,这就是我们在工厂类中保存的类的构造信息,这样我们就可以使用类名字符串创建对应的类了。代码如下 (源自 我所理解的 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <map>
#include <iostream>
#include <string>
using namespace std;

typedef void* (*PTRCreateObject)(void);

class ClassFactory {
private:
map<string, PTRCreateObject> m_classMap ;
ClassFactory(){}; //构造函数私有化

public:
void* getClassByName(string className);
void registClass(string name, PTRCreateObject method) ;
static ClassFactory& getInstance() ;
};

void* ClassFactory::getClassByName(string className){
map<string, PTRCreateObject>::const_iterator iter;
iter = m_classMap.find(className) ;
if ( iter == m_classMap.end() )
return NULL ;
else
return iter->second() ;
}

void ClassFactory::registClass(string name, PTRCreateObject method){
m_classMap.insert(pair<string, PTRCreateObject>(name, method)) ;
}

ClassFactory& ClassFactory::getInstance(){
static ClassFactory sLo_factory;
return sLo_factory ;
}

class RegisterAction{
public:
RegisterAction(string className,PTRCreateObject ptrCreateFn){
ClassFactory::getInstance().registClass(className,ptrCreateFn);
}
};

#define REGISTER(className) \
className* objectCreator##className(){ \
return new className; \
} \
RegisterAction g_creatorRegister##className( \
#className,(PTRCreateObject)objectCreator##className)

// test class
class TestClass{
public:
void m_print(){
cout<<"hello TestClass"<<endl;
};
};
REGISTER(TestClass);

int main(int argc,char* argv[]) {
TestClass* ptrObj=(TestClass*)ClassFactory::getInstance().getClassByName("TestClass");
ptrObj->m_print();
}

Taichi 反射库

Taichi Training类似于RTTR实现了一套动态反射库作为教程,教程链接40 分钟搓一个 C++ 反射库【原理、用法、实现都在这里了!】_哔哩哔哩_bilibili

首先,我们可以先看一段测试代码

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
35
36
37
class Foo {
public:
void PassByValue(std::string s) const {
std::cout << "Foo::PassByValue(`" << s << "`)" << std::endl;
}

void PassByConstRef(const std::string &s) const {
std::cout << "Foo::PassByConstRef(const `" << s << "` &)" << std::endl;
}

std::string Concat(const std::string &head, const std::string &tail) {
auto res = head + tail;
return res;
}

// std::unique_ptr will result in compile-time error
std::shared_ptr<float> MakeFloatPtr(float i) {
return std::make_shared<float>(i);
}

static void MakeReflectable() {
reflect::AddClass<Foo>("Foo")
.AddMemberVar("name", &Foo::name)
.AddMemberVar("x_", &Foo::x_)
.AddMemberFunc("PassByValue", &Foo::PassByValue)
.AddMemberFunc("PassByConstRef", &Foo::PassByConstRef)
.AddMemberFunc("Concat", &Foo::Concat)
.AddMemberFunc("MakeFloatPtr", &Foo::MakeFloatPtr);
}

int x() const { return x_; }

std::string name;

private:
int x_{0};
};

注册类型信息的代码就在MakeReflectable中,可以看到这里采用了一个建造者模式,可以链式的对成员函数和成员变量进行register。首先,在AddClass中会创建出这样的一个Builder

1
2
3
4
5
template <typename T>
details::TypeDescriptorBuilder<T> AddClass(const std::string &name) {
details::TypeDescriptorBuilder<T> b{name};
return b;
}

这样的Builder最终想要创建的就是一个TypeDescriptor

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
35
36
37
38
39
class TypeDescriptor {
public:
const std::string &name() const {
return name_;
}

const std::vector<MemberVariable> &member_vars() const {
return member_vars_;
}

const std::vector<MemberFunction> &member_funcs() const {
return member_funcs_;
}

MemberVariable GetMemberVar(const std::string &name) const {
for (const auto &mv : member_vars_) {
if (mv.name() == name) {
return mv;
}
}
return MemberVariable{};
}

MemberFunction GetMemberFunc(const std::string &name) const {
for (const auto &mf : member_funcs_) {
if (mf.name() == name) {
return mf;
}
}
return MemberFunction{};
}

private:
friend class RawTypeDescriptorBuilder;

std::string name_;
std::vector<MemberVariable> member_vars_;
std::vector<MemberFunction> member_funcs_;
};

成员变量

MemberVariable的实现中,使用了C++中的一种Type Eraser的方法,这样在编译时我们不需要类型信息,将类型信息的处理放在了运行时进行,也就是将类型的处理交给了使用者。也就是说这里MemberVariable本身不包含任何模板,类型的传递在构造函数以及getter和setter中使用模板进行推导。getter_setter_的参数和返回值都使用std::any,也就是任意类型,在更古老的版本中可以用void*。在getter_setter_的具体实现中,使用std::any_cast对类型进行强制类型转化。

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
35
36
37
38
class MemberVariable {
public:
MemberVariable() = default;

template <typename C, typename T>
MemberVariable(T C::*var) {
getter_ = [var](std::any obj) -> std::any {
return std::any_cast<const C *>(obj)->*var;
};
setter_ = [var](std::any obj, std::any val) {
// Syntax: https://stackoverflow.com/a/670744/12003165
// `obj.*member_var`
auto *self = std::any_cast<C *>(obj);
self->*var = std::any_cast<T>(val);
};
}

const std::string &name() const {
return name_;
}

template <typename T, typename C>
T GetValue(const C &c) const {
return std::any_cast<T>(getter_(&c));
}

template <typename C, typename T>
void SetValue(C &c, T val) {
setter_(&c, val);
}

private:
friend class RawTypeDescriptorBuilder;

std::string name_;
std::function<std::any(std::any)> getter_{nullptr};
std::function<void(std::any, std::any)> setter_{nullptr};
};

成员函数

成员函数中,类似的,也采用了Type Eraser的策略。这里的构造函数从一个变成了四个,分别对应 - void func(params...) - return_type func(params...) - void func(params...) const - return_type func(params...) const 四种情况。具体的实现中,C::*funcC时类的类型,func时成员函数,Args...时参数列表。绑定函数到fn_上调用时,首先会将类C和参数列表Args...转化成一个tuple的指针,然后调用std::apply函数执行函数返回结果。

另外,在调用的时候,使用std::reference_wrapper对类型进行了引用封装,用于确保该对象是作为一个引用保存在tuple中而不是值。

std::make_tuple Creates a tuple object, deducing the target type from the types of arguments. For each Ti in Types..., the corresponding type Vi in VTypes... is std::decay<Ti>::type unless application of std::decay results in std::reference_wrapper<X> for some type X, in which case the deduced type is X&.

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class MemberFunction {
public:
MemberFunction() = default;

template <typename C, typename R, typename... Args>
explicit MemberFunction(R (C::*func)(Args...)) {
fn_ = [this, func](std::any obj_args) -> std::any {
using tuple_t = std::tuple<C &, Args...>;
// How to debug compile-time types...
// static_assert(std::is_same<tuple_t, void>::value, "Hoi!");
auto *tp_ptr = std::any_cast<tuple_t *>(obj_args);
return std::apply(func, *tp_ptr);
};
}

template <typename C, typename... Args>
explicit MemberFunction(void (C::*func)(Args...)) {
fn_ = [this, func](std::any obj_args) -> std::any {
using tuple_t = std::tuple<C &, Args...>;
auto *tp_ptr = std::any_cast<tuple_t *>(obj_args);
std::apply(func, *tp_ptr);
return std::any{};
};
}

template <typename C, typename R, typename... Args>
explicit MemberFunction(R (C::*func)(Args...) const) {
fn_ = [this, func](std::any obj_args) -> std::any {
using tuple_t = std::tuple<const C &, Args...>;
// How to debug compile-time types...
// static_assert(std::is_same<tuple_t, void>::value, "Hoi!");
auto *tp_ptr = std::any_cast<tuple_t *>(obj_args);
return std::apply(func, *tp_ptr);
};
is_const_ = true;
}

template <typename C, typename... Args>
explicit MemberFunction(void (C::*func)(Args...) const) {
fn_ = [this, func](std::any obj_args) -> std::any {
using tuple_t = std::tuple<const C &, Args...>;
auto *tp_ptr = std::any_cast<tuple_t *>(obj_args);
std::apply(func, *tp_ptr);
return std::any{};
};
is_const_ = true;
}

const std::string &name() const {
return name_;
}

bool is_const() const {
return is_const_;
}

template <typename C, typename... Args>
std::any Invoke(C &c, Args &&... args) {
if (is_const_) {
auto tp = std::make_tuple(std::reference_wrapper<const C>(c), args...);
return fn_(&tp);
}
auto tp = std::make_tuple(std::reference_wrapper<C>(c), args...);
return fn_(&tp);
}

private:
friend class RawTypeDescriptorBuilder;

std::string name_;
bool is_const_{false};
std::function<std::any(std::any)> fn_{nullptr};
};

最终的测试和使用

测试代码如下,这里需要注意一点,对于const reference和reference的参数传递,需要手动添加一个std::refstd::cref的包装,否则会crash。这是因为否则存在tuple中的类型不是一个引用类型,这和我们希望的是冲突的,所以会产生问题。

这里还有另一个解决办法,作者通过一层中间层ArgWrap对形参到实参的转化作了处理,细节参考代码和视频

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
void TestFoo() {
std::cout << ">>> TestFoo\n" << std::endl;

Foo::MakeReflectable();
auto foo_t = reflect::GetByName("Foo");
for (const auto &mv : foo_t.member_vars()) {
std::cout << "member var: " << mv.name() << std::endl;
}
std::cout << std::endl;
for (const auto &mf : foo_t.member_funcs()) {
std::cout << "member func: " << mf.name() << ", is_const=" << mf.is_const()
<< std::endl;
}
std::cout << std::endl;

Foo f;
// Test member variables
auto name_var = foo_t.GetMemberVar("name");
name_var.SetValue(f, std::string{"taichi"});
std::cout << "f.name=" << f.name << std::endl;
auto x_var = foo_t.GetMemberVar("x_");
x_var.SetValue(f, 42);
std::cout << "f.x=" << f.x() << std::endl;
std::cout << std::endl;

// Test member functions
auto foo_make_float_ptr = foo_t.GetMemberFunc("MakeFloatPtr");
auto res = foo_make_float_ptr.Invoke(f, 123.4f);
auto float_sptr = std::any_cast<std::shared_ptr<float>>(res);
std::cout << "MakeFloatPtr res: " << *float_sptr << std::endl;

std::string hello_s{"hello"};
std::string world_s{" world"};

auto foo_pass_by_val = foo_t.GetMemberFunc("PassByValue");
foo_pass_by_val.Invoke(f, hello_s);

auto foo_pass_by_cref = foo_t.GetMemberFunc("PassByConstRef");
// foo_pass_by_cref.Invoke(f, hello_s); // Crash, value
// foo_pass_by_cref.Invoke(f, std::ref(hello_s)); // Crash, non-const ref
foo_pass_by_cref.Invoke(f, std::cref(hello_s)); // OK: const ref

auto foo_concat = foo_t.GetMemberFunc("Concat");
// foo_concat.Invoke(f, hello_s, world_s);
res = foo_concat.Invoke(f, std::cref(hello_s), std::cref(world_s));
std::cout << "Concat got: " << std::any_cast<std::string>(res) << std::endl;
std::cout << std::endl;

std::cout << "<<< TestFoo OK\n" << std::endl;
}

参考资料