搬运自己知乎文章:c++如何实现反射功能? - 知乎
c++的反射目前需要自行实现,核心思路就是把需要反射的class(struct)的成员、方法、构造函数等元数据通过数据结构描述出来,这些元数据可以在程序启动时构造出来存储在全局静态变量中。我们使用反射时通过名称或者类型查找到这些元数据,就可以new对象或者alloca对象,使用字符串查找到对象的成员或者方法,结合对象的指针再去访问他们。
以下内容都在https://github.com/laiyongcong/cppfoundation 中可找到详细实现。这是一个无入侵的,并且带有反射代码生成工具的开源库。结合CMake使用反射工具可以完全不需要手写任何额外的反射代码,此外,该开源库包含基于反射的json处理、csv文件读写(老游戏策划喜欢填表,这里加个狗头)、无锁的多线程框架和日志框架等。
下面简单讲述一下实现反射的基本原理,没有使用c++的奇怪的高级特性,甚至新手也能比较容易看懂。支持c++11的编译器就可以编译工程。
成员变量的描述是最简单的 ,一般包含以下信息:类型、offset、size、元素类型(数组情形)、数量(数组情形),以下是反射库的关于成员变量的描述:
class Field : public MemberBase {
friend class Class;
friend struct FieldRegister;
private:
const std::type_info& mType;
const std::type_info& mElementType; //for ArrayType
uint64_t mOffset;
std::size_t mSize;
int mElementCount; //for array type
int mFieldIdx; //field index in Class
public:
FORCEINLINE const std::type_info& GetType() const { return mType; }
FORCEINLINE const std::type_info& GetElementType() const { return mElementType; }
FORCEINLINE std::size_t GetSize() const { return mSize; }
FORCEINLINE bool IsArray() const { return mType != mElementType; }
FORCEINLINE int GetElementCount() const { return mElementCount; }
FORCEINLINE int GetFieldIdx() const { return mFieldIdx; }
FORCEINLINE uint64_t GetOffset() const { return mOffset; }
template <typename Object, typename Value>
void Get(const Object* object, Value& result) const;
template <typename Object, typename Value>
void GetPtr(const Object* object, Value*& result) const;
template <typename Object, typename Value>
void GetByIdx(const Object* pObj, Value& result, int idx = 0) const;
template <typename Object, typename Value>
void GetPtrByIdx(const Object* pObj, Value*& result, int idx = 0) const;
template <typename Object, typename Value>
void Set(Object* object, const Value& value) const;
template <typename Object, typename Value>
void SetByIdx(Object* object, const Value& value, int idx = 0) const;
private:
Field(uint64_t offset, std::size_t size, const std::type_info& type, const std::type_info& elementType, const Class* pClass, EnumAccessType accessType, const char* strType, const char* strName,
int nElementCount);
};
在反射数组时,需要推导数组的元素个数,当我们需要把一个类的成员变量反射时,一般的反射函数会这样写:
template <typename F>
void RefField(const F T::* fieldptr, const char* szName){
……
}
这里F代表成员的类型,T就是需要反射的class(struct),F如果是一个数组,表面上看我们很难得到其元素的类型和元素的个数,为了得到这些信息我们需要把它定义出来:
static char szTmpBuff[sizeof(F)];
F* pField = (F*)szTmpBuff;
我们得到了一个F类型的实例,接下来我们需要利用模版进行推导元素的类型以及数量(可能有其他方法):
struct FieldInfo {
const std::type_info& mElementType;
int mElementCount;
FieldInfo(int nCount, const std::type_info& type) : mElementType(type), mElementCount(nCount){}
};
template <typename F, int N>
FieldInfo GetFieldInfo(F (&c)[N]) {
return FieldInfo(N, typeid(F));
}
template <typename F>
FieldInfo GetFieldInfo(F(&c)) {
return FieldInfo(1, typeid(F));
}
....
//此时我们只需要调用GetFieldInfo就可以得到元素的类型以及数量
FieldInfo fInfo = GetFieldInfo(*pField);
成员函数和构造函数的描述复杂一些,因函数的返回值,参数表等都是不确定的,需要进行模版推导,首先我们需要对一个参数列表进行推导,以获得函数的参数列表信息:
template<typename T, typename... Args>
struct ArgsHelper {
static void ListArgs(TypeList& argList) {
if (typeid(T) != typeid(void)) argList.push_back(&typeid(T));
ArgsHelper<Args...>::ListArgs(argList);
}
};
template <typename T>
struct ArgsHelper<T> {
static void ListArgs(TypeList& argList) {
if (typeid(T) != typeid(void)) argList.push_back(&typeid(T));
}
};
然后我们需要对整个函数做一个基本的描述(函数都有返回值以及参数表):
struct BaseCallable {
virtual ~BaseCallable(){}
virtual int32_t GetArgsCount() const = 0;
virtual const TypeList& GetArgTypeList() const = 0;
virtual const std::type_info& GetRetType() const = 0;
virtual String GetArgsString() const = 0;
};
template <typename R, typename... Args>
struct Callable : public BaseCallable {
TypeList ArgList;
Callable() { ArgsHelper<Args...>::ListArgs(ArgList); }
int32_t GetArgsCount() const override { return (int32_t)ArgList.size(); }
virtual const TypeList& GetArgTypeList() const override { return ArgList; }
virtual const std::type_info& GetRetType() const override { return typeid(R); }
virtual String GetArgsString() const override {
String strRes = "(";
auto argsList = GetArgTypeList();
auto it = argsList.begin();
if (it != argsList.end()) {
strRes += Demangle((*it)->name());
it++;
}
for (; it != argsList.end(); it++) {
strRes += ",";
strRes += Demangle((*it)->name());
}
strRes += ")";
return strRes;
}
};
//空参数表情形
template <typename R>
struct Callable<R> : public BaseCallable {
TypeList ArgList;
Callable() {}
int32_t GetArgsCount() const override { return 0; }
virtual const TypeList& GetArgTypeList() const override { return ArgList; }
virtual const std::type_info& GetRetType() const override { return typeid(R); }
virtual String GetArgsString() const override { return "()"; }
};
在此基础上,我们继续对函数进行进一步的描述(带上调用方法),如果是静态函数,可以描述成以下的数据结构:
template <typename R, typename... Args>
struct StaticCallable : public Callable<R, Args...> {
typedef R (*MethodType)(Args...);
MethodType method;
StaticCallable(MethodType m) : Callable<R, Args...>(), method(m) {}
R invoke(Args... args) const { return method(args...); }
};
template <typename R>
struct StaticCallable<R> : public Callable<R> {
typedef R (*MethodType)();
MethodType method;
StaticCallable(MethodType m) : Callable<R>(), method(m) {}
R invoke() const { return method(); }
};
如果是class(struct)的成员函数,我们这样描述:
template <typename R, typename C, typename... Args>
struct MemberFuncCallable : public Callable<R, Args...> {
typedef R (C::*MethodType)(Args...);
MethodType method;
MemberFuncCallable(MethodType m) : Callable<R, Args...>(), method(m) {}
R invoke(C* object, Args... args) const { return (object->*method)(args...); }
};
template <typename R, typename C>
struct MemberFuncCallable<R, C> : public Callable<R> {
typedef R (C::*MethodType)();
MethodType method;
MemberFuncCallable(MethodType m) : Callable<R>(), method(m) {}
R invoke(C* object) const { return (object->*method)(); }
};
成员函数如果是常函数,我们也需要考虑,需要把函数指针类型换成typedef R (C::*MethodType)(Args...) const; 这里不展开。
至此,描述函数的Method除了其他描述性信息,关键的是需要存储一个BaseCallable指针,用该指针结合模版,正确地调用到目标函数,如下:
template <typename R, typename C, typename... Args>
R Method::Invoke(C* object, Args... args) const {
typedef const MemberFuncCallable<R, C, Args...> CallableType;
typedef const ConstMemberFuncCallable<R, C, Args...> ConstCallableType;
CallableType* cb = dynamic_cast<CallableType*>(mCallable.get());
if (cb) {
return cb->invoke(object, args...);
}
ConstCallableType* constCb = dynamic_cast<ConstCallableType*>(mCallable.get());
if (constCb) {
return constCb->invoke(object, args...);
}
if (TestCompatible<Args...>(typeid(R), typeid(C), object, mIsVirtual)) {
cb = (CallableType*)(mCallable.get());
return cb->invoke(object, args...);
}
throw TypeMismatchError(GetLongIdentity() + ":\n" + FindMisMatchedInfo<Args...>(typeid(R), typeid(C)));
}
关于类的构造函数的反射以及在内存上alloca方法的反射,可以参考ReflictiveClass中的RefConstructor()方法,这里涉及推导构造函数,以及从构造函数推导alloca函数的过程。
我们已经有了描述代码中基本元数据的数据结构,我们需要一个描述一个class(struct)的数据结构:
class Class {
……
private:
mutable std::atomic_int mFieldIdx;
String mName;
String mFullName;
mutable const Class* mSuper; //继承关系
const SuperCastFuncPtr mSuperCastFunc;
const SuperCastConstFuncPtr mSuperCastConstFunc;
const std::type_info& mType;
const std::type_info& mPtrType;
FieldList mFields;
FieldMap mFieldMap;
StaticFieldList mStaticFieldList;
StaticFieldMap mStaticFieldMap;
MethodList mMethods;
MethodMap mMethodMap;
StaticMethodList mStaticMethods;
StaticMethodMap mStaticMethodMap;
ConstructorList mConstructors;
};
我们只需要把所有的Class管理起来,整个反射就具备了基本框架。如何便利地把需要反射的class(struct)注册到系统中,可以参考ReflectiveClass的实现,这里不展开。
程序员一般都很懒,能自动实现的一般不想逐行敲代码,我们需要一个自动生成反射代码的工具,在这个开源库中,提供了一种基于llvm+clang进行代码生成的工具,在我们程序代码中只需要把需要反射的成员、函数打上下面的标记,利用反射工具就可以自动生成代码。
#ifndef REF_EXPORT_FLAG
# define REF_EXPORT
#else
# define REF_EXPORT struct CPPFD_JOIN(__refflag_, __LINE__) {};
#endif
反射工具会分析头文件,由于反射工具的REF_EXPORT_FLAG宏是生效的, 因此它看到标记了需要反射的成员前面都会多出了一个特殊的struct,基于此就可以把需要反射的信息提取出来,并生成我们需要的代码。
反射工具的源代码也在提供了,可以根据自己的项目进行扩展和修改。
1547

被折叠的 条评论
为什么被折叠?



