由于c++原生不支持反射,绝大多数情况也不需要反射,但是有些时候比如在读取配置文件或者数据表,如果通过反射可以避免写大量的重复无用的代码,提高开发效率。因此需要 c++的反射的实现满足下面的条件
1 不需要一股脑给所有的类都加反射不需要也没有必要,可以选择性的给需要的类加反射,毕竟加了反射以后肯定会有性能的消耗,这是我不想看到的。
2 不要改变c++的编程习惯,用起来不要太复杂
3 尽可能在编译器完成反射的注册(非必选,运行时注册科已在启动的时候完成所有反射类的注册)
4 有比较全面的类型错误检测,防止反射类型发生错误
5 支持的类型要全,对于代码来说,支持99%的类型和没有支持是一样的,不仅要支持基本类型的反射还要支持对象和stl容器的反射,枚举等等
6 不要改变对象的内存结构,尤其是pod类型的对象,对于pod类型我可以用内存池,可以可以memcpy和memmov,如果改了对象的内存结构,会有诸多的问题
7 不要破坏类的声明结构,否则代码的可读性会变得很差
8 实现很干净,不要因为引入一个简单的反射库装了一堆的其他的没用过的库比如尤其是boost这种大库
通过一段时间的相关查阅发现现存的实现方案,列举下感觉上可以用的方案
一 给代码加标签然后用工具分析自动生成代码
1 unreal引擎(实现在C++源文件中空的宏做标记,然后用UHT分析生成generated.h/.cpp文件)
UCLASS()
class HELLO_API UMyClass : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Category = "Test")
float Score;
UFUNCTION(BlueprintCallable, Category = "Test")
void CallableFuncTest();
UFUNCTION(BlueprintNativeEvent, Category = "Test")
void NativeFuncTest();
UFUNCTION(BlueprintImplementableEvent, Category = "Test")
void ImplementableFuncTest();
};
2 qt图形界面库(利用基于moc(meta object compiler)实现,用一个元对象编译器在程序编译前,分析C++源文件,识别一些特殊的宏Q_OBJECT、Q_PROPERTY、Q_INVOKABLE……然后生成相应的moc文件,之后再一起全部编译链接)。
#include <QObject>
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(int Member1 READ Member1 WRITE setMember1 )
Q_PROPERTY(int Member2 READ Member2 WRITE setMember2 )
Q_PROPERTY(QString MEMBER3 READ Member3 WRITE setMember3 )
public:
explicit MyClass(QObject *parent = 0);
signals:
public slots:
public:
Q_INVOKABLE int Member1();
Q_INVOKABLE int Member2();
Q_INVOKABLE QString Member3();
Q_INVOKABLE void setMember1( int mem1 );
Q_INVOKABLE void setMember2( int mem2 );
Q_INVOKABLE void setMember3( const QString& mem3 );
Q_INVOKABLE int func( QString flag );
private:
int m_member1;
int m_member2;
QString m_member3;
};
这两个功能非常完善,使用起来非常简单优雅,都是简单的给类和相关的变量和函数加上标签,然后通过自己实现的工具分析代码生成先关的代码(对用户是透明的)不需要关系中间的复杂过程和生成的复杂代码,遗憾的是都需要基于各自的开发工具才能使用,我们不能直接使用。
3 https://github.com/Leandros/metareflect ,Metareflect is a lightweight reflection system for C++, based on LLVM and Clangs libtooling.,一个类似于上面的一种解决方案,也是加标签然后分析代码的方式,不过使用方法略微复杂。
CLASS() Point
{
public:
PROPERTY()
int x;
PROPERTY()
int y;
PROPERTY()
int z;
FUNCTION()
size_t Hash() const
{
return x ^ y ^ z;
}
};
二 通过宏定义或者额外的代码的方式手动注册记录反射信息
4 https://github.com/garettbass/reflect ,easy reflection and serialization for C++17,破坏了类的声明结构,并且要支持c++17
struct example_struct {
// use the reflect_fields macro to declare reflected fields
reflect_fields(
((int),i),
((float),f),
((std::string),s) // NOTE: no trailing comma here
)
};
5 https://github.com/skarupke/reflection,通过宏定义手动注册
using namespace metav3;
struct memcpy_speed_comparison
{
float vec[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
int i = 0;
float f = 0.0f;
template<typename Ar>
void serialize(Ar & archive, int)
{
archive & vec;
archive & i;
archive & f;
}
};
REFLECT_CLASS_START(memcpy_speed_comparison, 0)
REFLECT_MEMBER(vec);
REFLECT_MEMBER(i);
REFLECT_MEMBER(f);
REFLECT_CLASS_END()
6 https://github.com/skypjack/meta ,通过额外代码注册
meta::reflect<my_type>(hash("reflected"))
.func<&my_type::static_function>(hash("static"))
.func<&my_type::member_function>(hash("member"))
.func<&free_function>(hash("free"));
7 https://www.codeproject.com/Articles/8712/AGM-LibReflection-A-reflection-library-for-C
class Base {
public:
//needed so as that the class gets reflection capabilities
CLASS(Base, NullClass);
//a reflected property
PROPERTY(int, length);
//a reflected method
METHOD(public, bool, processLength, (int l));
private:
//a reflected field
FIELD(private, int, m_length);
//property getter
int get_length() const {
return m_length;
}
//property setter
void set_length(int l) {
m_length = l;
}
};
虽然说实现很简单吗,只有一个头文件很简洁,但是破坏了声明结构并且对于容器不支持,对于<>宏定义展开后,会编译报错
8 https://github.com/billyquith/ponder
class Person
{
public:
// constructor
Person(const std::string& name)
: m_name(name)
{}
// accessors for private members
std::string name() const { return m_name; }
void setName(const std::string& name) { m_name = name; }
// public members
float height;
unsigned int shoeSize;
// member function
bool hasBigFeet() const { return shoeSize > 10; } // U.K.!
private:
std::string m_name;
};
//! [eg_simple_class]
//! [eg_simple_declare]
PONDER_TYPE(Person) // declare the type to Ponder
static void declare() // declare the class members to Ponder
{
ponder::Class::declare<Person>("Person")
.constructor<std::string>()
.property("name", &Person::name, &Person::setName)
.property("height", &Person::height)
.property("shoeSize", &Person::shoeSize)
.function("hasBigFeet", &Person::hasBigFeet)
;
}
9 rttr
github地址:https://github.com/rttrorg/rttr
#include <rttr/registration>
using namespace rttr;
struct MyStruct { MyStruct() {}; void func(double) {}; int data; };
RTTR_REGISTRATION
{
registration::class_<MyStruct>("MyStruct")
.constructor<>()
.property("data", &MyStruct::data)
.method("func", &MyStruct::func);
}
type t = type::get<MyStruct>();
for (auto& prop : t.get_properties())
std::cout << "name: " << prop.get_name() << std::endl;
for (auto& meth : t.get_methods())
std::cout << "name: " << meth.get_name() << std::endl;
以上是几种感觉还可的实现方案,但也不是全都能用1和2肯定用不了,3是可以用的(但是生成的代码的安全性没法保证),4和7破坏了类的声明结构不考虑了,5,6,8和9其实原理是一样的手动注册,然后就是分析源码我选择了rttr,他的github标星最多,有自己的官网,文档齐全。特性齐全:
1 反映构造函数,方法,数据成员或枚举
2 类; 具有单继承,多继承和虚拟继承
3 构造函数(任意参数计数)
4 方法(虚拟,抽象,重载,任意参数计数)
5 数组(包括原始数组;任意维数)
6 从任意类级别调用类的属性和方法的能力
7 无割台污染;在cpp文件中创建反射信息,以最大程度地减少修改数据时的编译时间
8 使用自定义类型,而无需在编译时声明类型(对插件有用)
9 向所有反射对象添加其他元数据的可能性
10 向方法或构造函数添加默认参数的可能性
11 通过政策调整注册行为
12 最小的宏使用
13 无需其他第三方依赖
14 无需 rtti;包含更快且跨共享库的工作替换
15 没有例外(此功能需要付费,并且在控制台上也经常禁用)
16 无需外部编译器或工具,仅需标准ISO C ++ 11
使用简单,不破坏声明结构,没有依赖支持类型齐全,不破坏内存结构(pod类型),对于非pod类型如果需要检索类的继承关系图时需要在勒种加RTTR_ENABLE
struct A { RTTR_ENABLE() };
struct B : A { RTTR_ENABLE(A) };
struct C : B { RTTR_ENABLE(B) };
/************************************************************************************
* *
* Copyright (c) 2014 - 2018 Axel Menzel <info@rttr.org> *
* *
* This file is part of RTTR (Run Time Type Reflection) *
* License: MIT License *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the "Software"), *
* to deal in the Software without restriction, including without limitation *
* the rights to use, copy, modify, merge, publish, distribute, sublicense, *
* and/or sell copies of the Software, and to permit persons to whom the *
* Software is furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
* SOFTWARE. *
* *
*************************************************************************************/
#ifndef RTTR_RTTR_ENABLE_H_
#define RTTR_RTTR_ENABLE_H_
#include <type_traits>
#include "rttr/type.h"
#include "rttr/type_list.h"
#ifdef DOXYGEN
/*!
* \brief This macro is necessary in order to retrieve type information about the
* inheritance graph of a class. When there is no inheritance graph, the macro is **not** needed, e.g. for POD types.
*
* Put the macro inside every class, where you need the complete inheritance information about the class type.
*
* \code{.cpp}
* struct Base
* {
* RTTR_ENABLE()
* };
* \endcode
*
* Place the macro \ref RTTR_ENABLE() somewhere in the class, it doesn't matter if its under the public,
* protected or private class accessor section. The macro will close itself with a `private` visibility.
* So when you not specify anything afterwords, everything will be `private`.
*
* Into the derived class you put the same macro, but now as argument the name of the parent class.
* Which is in this case `Base`.
* \code{.cpp}
* struct Derived : Base
* {
* RTTR_ENABLE(Base)
* };
* \endcode
*
* When you use multiple inheritance you simply separate every class with a comma.
* \code{.cpp}
* struct MultipleDerived : Base, Other
* {
* RTTR_ENABLE(Base, Other)
* };
* \endcode
*
* \remark Without this macro, it will not be possible to use \ref rttr::rttr_cast "rttr_cast" or
* meta information in the type class, like: \ref rttr::type::get_base_classes() "get_base_classes()" or
* \ref rttr::type::get_derived_classes() "get_derived_classes()".
*/
#define RTTR_ENABLE(...)
#else
#define TYPE_LIST(...) ::rttr::type_list<__VA_ARGS__>
#define RTTR_ENABLE(...) \
public:\
RTTR_BEGIN_DISABLE_OVERRIDE_WARNING \
virtual RTTR_INLINE ::rttr::type get_type() const { return ::rttr::detail::get_type_from_instance(this); } \
virtual RTTR_INLINE void* get_ptr() { return reinterpret_cast<void*>(this); } \
virtual RTTR_INLINE ::rttr::detail::derived_info get_derived_info() { return {reinterpret_cast<void*>(this), ::rttr::detail::get_type_from_instance(this)}; } \
using base_class_list = TYPE_LIST(__VA_ARGS__); \
RTTR_END_DISABLE_OVERRIDE_WARNING \
private:
#endif // DOXYGEN
#endif // RTTR_RTTR_ENABLE_H_
它会给类的对象的内存头加一个虚函数表指针,当然如果类本身就有虚函数,也就有虚函数表,加一个虚函数,就不会出现这种情况,或者说如果你不需要检索类的继承图,仅仅反射对象的成员变量和成员函数,那也不需要这个宏定义,也不会修改类的内存结构,在这点上可以接受。
唯一不足的是源代码大量运用了c++11和模版元编程,可以说把c++11和模版元编程发挥到了极致,读起来比较难懂容易偏头疼,不过在类型安全的检测上还是做得很到位的。
最后总的来说倾向于两个选择:
1 第三种 metareflect加标签的方式(用起来是最舒服的,也是最优雅的)
2 最后一种rttr,虽然用起来稍微麻烦点需要手动注册但是代码的可控性和安全性很高,个人比较倾向于这种。
参考链接:
https://www.ctolib.com/mip/Cmdu76-AwesomeCppGameDev.html
https://zhuanlan.zhihu.com/p/24445322