0%

Reflection in C++

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <string>
struct Test {
int index;
std::string name;
void printInfo() const {
std::cout << "index: " << index << ", name: " << name << "\n";
}
};
int main()
{
Test test;
test.index = 1;
test.name = "test_1";
test.printInfo();
auto index_addr = &Test::index;
auto name_addr = &Test::name;
auto fun_print_addr = &Test::printInfo;
test.*index_addr = 2;
test.*name_addr = "test_2";
(test.*fun_print_addr)();
return 0;
};

透過上面的index_addrname_addrfun_print_addr等,可以對object進行操作
而反射主要分成兩部分

  • Metadata generation
    和C++ object有關的information就叫做metadata,如上面的例子,這邊的困難點是如何減少工作量
  • Metadata Reflection
    既然有了Metadata,如何跟現實使用上連結起來

雖然目前的官方標準還沒出來,不過現在有兩大流派

手工打造

什麼辦不到的事情,用Marco就好了
以Boost Describe舉例

1
2
3
4
5
6
struct X
{
int m1;
int m2;
};
BOOST_DESCRIBE_STRUCT(X, (), (m1, m2))

其他Macro Based的方案也差不多,就是另外定義一個Macro,自動生成類似上面的Metadata
不過這邊的問題就是

  • 你要同時維護兩份資料的一致性
  • Macro滿天飛
  • 修改困難 (因為都是Marco的黑魔法,要新增功能就得對Marco動刀)

libclang

另外一派就是借助libclang來動手生成,透過Parse C++ AST來生成需要的API
舉例說明

1
2
3
4
5
6
7
8
class MyClass
{
public:
int field = 0;
static int static_field;
void method();
static void static_method();
};

生成的Metadata可以這麼使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
reflang::Class<MyClass> metadata;
MyClass c;

// Modify / use c's 'field'.
reflang::Reference ref = metadata.GetField(c, "field");
ref.GetT<int>() = 10;

// Modify / use 'static_field'.
ref = metadata.GetStaticField("static_field")
ref.GetT<int>() = 10;

// Execute 'method()'.
auto methods = metadata.GetMethod("method");
(*methods[0])(c);

// Execute 'static_method()'.
auto methods = metadata.GetStaticMethod("static_method");
(*methods[0])();

這個方案的問題在於

  • 要有libclang才能用
  • 構建的時候會多一個步驟,必須掃描所有的檔案,生成需要的header/sources,修改Makefile/CMakeLists.txt來調整編譯流程

Reflection API in the future

雖然現有的Reflection library多的跟山一樣,不過眾口難調,有些是針對特定用途設計的,無法涵蓋其他方面的使用,有些功能完整,但是難用
於是乎就有人想要對語法方面下手,成為C++ Standard中的一部分

1
2
3
4
5
6
7
8
template <class T>
void print_type() {
std::cout << "void "
<< get_name_v<reflexpr(print_type<T>)> // guaranteed "print_type"
<< "() [with T = "
<< get_display_name_v<reflexpr(T)>
<< "]" << std::endl;
}

reflexpr和decltype一樣是type-based,所以可以套用到type based metaprogramming中
不過會不會成為標準是另外一回事了
跟Network Library一樣,成為標準之前先用成熟的方案解決

Reference