c++实现反射功能

部署运行你感兴趣的模型镜像

搬运自己知乎文章: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,基于此就可以把需要反射的信息提取出来,并生成我们需要的代码。

反射工具的源代码也在提供了,可以根据自己的项目进行扩展和修改。

您可能感兴趣的与本文相关的镜像

Seed-Coder-8B-Base

Seed-Coder-8B-Base

文本生成
Seed-Coder

Seed-Coder是一个功能强大、透明、参数高效的 8B 级开源代码模型系列,包括基础变体、指导变体和推理变体,由字节团队开源

评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值