c++ complete runtime type information of dynamic type information

  • 2020-05-26 09:52:03
  • OfStack

As we all know, code apes write code, nature requires rigorous and meticulous, but imagination is also very important. This code reading several ten years, is absolutely very many yards apes imagination greatly imprison, few people can cross the step 1, especially c + + classmates, together with the commission's 1 tuo old man, is to let a person speechless, said by the works of these people, are like a dead fish eyes, 1, one thousand people no clever and the joy of life. The stl, boost libraries all look like this (although they do perform most everyday tasks), not to mention the other libraries, which are nothing new.

Let's talk about dynamic type information, or reflection. Naturally, the language itself provides a waste of type_info, which is of no use except to prove that c++ is a fake and can also support dynamic information. Who would do something with type_info? As a result, everyone is trying to make up for the lack of runtime type information in c++. Because the type of reflection information is too important, or, reflection is used too much, many things do not need reflection on the surface, or the literal code can not see the trace of reflection, but the implementation of the inside, a lot of reflection in the glow. c++ insists on not giving 1 extra bit of support on dynamic information, which does not mean that c++ does not need reflection. Let's look at the standard library, which tries to avoid dynamic polymorphism, and see what kind of failed works it is. If stl1 had not rejected dynamic polymorphism so vigorously from the beginning, you can see that even the memory allocation of allocator can be done in the static type information (the latest version of c++ will finally accept the polymorphic allocator, c++ bound is a piece of cheer, it is really sad), today's c++ will not be in many areas of the division and sum.

In general, now on the market of c + + reflection library, are invasive, all learn to mfc that 1 set, is inherited from a base class Object, then can provide the function of the reflection information, regardless of whether they provide the type of information is complete, it will limit the versatility to die in a very narrow circle inside is very narrow. These reflection libraries, 1, cannot reflect basic types, int, char, double, const char*... And so on; 2. You cannot reflect class or struct that are not inherited from Object, and 3. You cannot reflect template classes, such as vector < int > , list < vector < vector < int > > > . Although typeid is thousands of weak chicken, it is also not 1, at least non-invasive, equal, polymorphic. Therefore, the ideal reflection should be as colorless and odorless as c++ native typeid: 1. Non-invasive; 2. 2. It can provide reflection for all types, such as basic types, struct of non-Object or class or template; 3, polymorphic, as long as the change of the type requires the type recognition at runtime, then return its own type (subclass), rather than the literal declaration of the type; 4, support type parameters, that is, when the type is passed to the function, the corresponding type information object is returned.

To be specific, the reflection library we want looks like this. Of course, you first have a type information object, TypeInfo, which is full of all the details about the type. As follows: you can guess from this reflection that the framework only supports single inheritance, which is intentional.

struct TypeInfo
  template<typename Args>
  void ConstructObject(void* obj, MemoryAllocator* alloc, Args&& args)const ; 
  bool IsDerviedOf(const TypeInfo* base)const;

  virtual TIType GetTIType()const = 0;
  virtual const InterfaceMap* GetInterfaces()const ; 
  virtual jushort GetMemorySize()const ; 
  virtual ConstText GetName() const ; 
  virtual AString GetFullName()const ; 
  virtual jushort GetAlignSize() const ; 
  virtual ConstText GetSpaceName()const;
  virtual const TypeInfo* GetBaseTypeTI()const;
  virtual const TypeInfo* GetPointeedTI()const ; 
  virtual size_t GetHashCode(const void* obj)const;
  virtual bool IsValueType()const { return true; }
  virtual bool IsClass()const { return true; }

  virtual bool DoInitAllocator(void* obj, MemoryAllocator* memAlloc)const;
  virtual bool NeedDestruct()const { return false; }
  virtual void DoDefaultConstruct(void* obj)const;
  virtual bool CanDefaultConstruct()const { return true; }
  virtual void DoAssign(void* dest, const void* src)const;
  virtual bool Equals(const void* objA, const void* objB)const;
  virtual void DoDestruct(void* obj)const;

And then, you have to have one function, TypeOf, which should be two, and one is a type template function with no arguments, which you can call, TypeOf < type > (a); One is a type template function with one argument, which can be called TypeOf(obj). Either way, the return result is const TypeInfo*. What TypeOf does is, for each type, there is only one one-only TypeInfo object corresponding to it, whether template or non-template; For example, the following judgments must be true.
TypeOf < int > () == TypeOf < int > ();
TypeOf < int > () = = TypeOf (n); / / n as integer
TypeOf < vector < int > > () = = TypeOf (nums); //nums is of type vector < int >
Object* a = new ObjectA; TypeOf(a) == TypeOf < ObjectA > ();
In fact, there is nothing magical about the principle here, except that trait is combined with sfine, and then it is all coolie work, which is to specialize a detailed description of the type object for each type. Macros can save a lot of code. But the entire reflection library, which has been refactored a dozen times and is still being refactored, has finally solved all sorts of development issues. For example, serialization (support for Pointers, support for polymorphism), interchangeability between objects and xml, interchangeability between objects and json, database table read-write objects, formatting, Any types, non-intrusive interfaces, message sending, string generation objects, and so on.
The way to do this, in a nutshell, is to introduce the indirect layer element function TypeInfoImp, which is specifically used to return a type type, type with an GetTypeInfo() function. Then TypeOf calls type's GetTypeInfo() in TypeInfoImp to get the TypeInfo object. The code is shown below.

template<typename Ty> struct TypeInfoImp
  typedef Ty type;
  static const bool value = THasGetTypeInfoMethod<Ty>::value;

 template<typename Ty>
 struct TypeInfoImp<const Ty> : public TypeInfoImp<Ty>
  typedef typename TypeInfoImp<Ty>::type type;
  static const bool value = TypeInfoImp<Ty>::value;
 template<typename Ty>
 const TypeInfo* TypeOf()
  typedef typename TypeInfoImp<Ty>::type TypeInfoProvider;
  return TypeInfoProvider::GetTypeInfo();
 template<typename Ty>
 const TypeInfo* TypeOf(const Ty& obj)
  typedef typename IsRttiType<Ty>::type is_rtti; // Also is the indirect layer, to the dynamic type and the non-dynamic type processing respectively 
  return ImpTypeOf(obj, is_rtti());
 struct TypeInfoImp < bool >
  static const bool value = true;
  typedef TypeInfoImp<bool> type;
  static TypeInfo* GetTypeInfo();
 TypeInfo* TypeInfoImp<bool>::GetTypeInfo()
  static TypeInfo* ti = CreateNativeTypeInfo<bool>("bool");
  return ti;

There may be some neat ways to do this, such as not having to introduce TypeInfoImp, but it turns out that TypeInfoImp's approach is the most flexible and saves the most code. At the very least, it is very convenient in the custom struct or class, as long as the struct contains a function of GetTypeInfo(), it can be included in the TypeOf system, very convenient. For TypeInfoImp of the template type, this is where the hash table comes in. For example, for the type information of std::paira, the following implementation,

template<typename FstTy, typename SndTy>
struct TypeInfoImp < std::pair<FstTy, SndTy> >
static const bool value = true;
typedef TypeInfoImp < std::pair<FstTy, SndTy> > type;
static TypeInfo* GetTypeInfo()
ParamsTypeInfo<FstTy, SndTy> args;
return PodPair::LookupTemplateTypeInfo(args);

Extract the const TypeInfo* of its type parameter, generating an array. Use this array to look in the hash table of PodPair. If the hash table contains an object with an array argument of this type, it is returned. Otherwise, see create 1, add 1 hash entry, and return. Each generic type, such as vector, list, and pair, has its own hash table.
Call it a day. The principle is simple, but for industrial-scale reflection libraries, there are many details to consider, such as the memory management of TypeInfo objects; How to generate 1 heap of strings for enum types to support the conversion of strings to enume values; Generate and save the constructor and destructor Pointers for class; Namespace support; attribute in C#; How to generate member fields or member function information in the most convenient way, etc., 1 sentence, is fucking manual labor. However, the reward is very abundant, after the coolie work here, the rest of the program, basically, there is no duplication of similar code, 1 cut of the physical work can be pressed on the type information here.

Related articles: