C++ writes non intrusive interfaces

  • 2020-05-26 09:54:31
  • OfStack

Finally, to the non-intrusive interface of c++, excited, happy, disappointed, relieved... . After doing so many object-oriented science, I have begun to be impatient, so far, do not want to do too much to elaborate.

Although it was clear early on how to do non-intrusive interfaces with c++, the entire framework code was refactored a dozen times before it was finally satisfied. Support for adding interfaces to base types, such as int, char, const char*, double; Support generics, such as vector, list; Support inheritance, the implementation of the interface of the base class, means that the subclass also inherited the implementation of the interface, and the subclass can also refuse the interface of the base class, like the duck refused the base class birds "can fly", compile times error; Support interface composition; ... However, this is just a brief introduction of its principle, which does not involve the processing of various abnormal details in C++, C++, but for those who want to do a little bit of serious work, you will face an endless tangle of details.

Let's look at some examples:

1. Naturally, define an interface: take it from a real code snippet


  struct IFormatble
  {
    static TypeInfo* GetTypeInfo();
    virtual void Format(TextWriter& stream, const FormatInfo& info) = 0;
    virtual bool Parse(TextReader& stream, const FormatInfo& info)
    {
      PPNotImplement();
    }
  };

2. Implementation class of interface. If the interface implementation of IFormatble is added to int, the actual code will definitely not write the code of implementation class for one basic type after another. This is just an illustration. I'm just going to name the class,


  struct ImpIntIFormatble : IFormatble
  {
    int* mThis;  // this 1 Line is the key 
    virtual void Format(TextWriter& stream, const FormatInfo& info)override
    {}

    virtual bool Parse(TextReader& stream, const FormatInfo& info)override
    {}
  };


The key here is that the fields of the implementation class are specified to contain no more than three pointer member fields, and the first field 1 must be a pointer to the destination type, the second is a type information object (for generics), and the third is an extra parameter, in no disordered order. If you don't need the second and third member field data, you can omit it, like here. All interface implementation classes must adhere to this memory layout;

3. Assembly: assemble the implementation class of the interface to the existing class to tell the compiler that the implementation class of a certain interface (in this case, IFormatble) is implemented with the implementation class ImpIntIFormatble of step 2;

PPInterfaceOf(IFormatble, int, ImpIntIFormatble)

4. Register the implementation class in the list of interface implementations for type information. This step can be omitted just for run-time interface queries, equivalent to Query of IUnknown. This 1 line of code is executed in the constructor of the global object in the cpp source file

RegisterInterfaceImp<IFormatble, int>();

Then you can happily use the interface, for example


      int aa = 20;
      TextWriter stream();
      FormatInfo info();
      TInterface<IFormatble> formatable(aa); //TInterface This name too ugly, also can't help 
      formatable->Format(stream, info);
      double dd = 3.14;
      formatable = TInterface<IFormatble>(dd);  // Assuming that double Also realize IFormatble
      formatable->Format(stream, info);

Is it a little magical? It's nothing, but it's all about trait and memory layout, which is just a type manipulation trick. Consider the memory layout of ImpIntIFormatble. For the common c++ compiler, the virtual function table pointer of the object (if it exists) is placed on the starting address of the object, followed by the member data field of the object itself. Therefore, the memory layout of ImpIntIFormatble is equivalent to,


struct ImpIntIFormatble
{
  void* vtbl;
  int* mThis;
};
 

Notice that there is no inheritance left. This is the in-memory representation of the ImpIntIFormatble object that implements the IFormatble interface. Therefore, it is conceivable that the memory layout of all interface implementation classes is enforced in the following form:


  struct InterfaceLayout
  {
    const void* mVtbl;
    const void* mThis;      // The object itself 
    const TypeInfo* mTypeInfo;  // The type information 
    const void* mParam;  // Supplementary parameters, 1 It's rarely used 
  };



Of course, you can't play this game if the compiler's vtable pointer isn't at the object's starting address, and you can't start with a non-intrusive interface. And then you have TInterface, which is inherited from InterfaceLayout


  template<typename IT>
  struct TInterface : public InterfaceLayout
  {
    typedef IT interface_type;
    static_assert(is_abstract<IT>::value, "interface must have pure function");
    static_assert(sizeof(IT) == sizeof(void*), "Can't have data");
  public:
    interface_type* operator->()const
    {
      interface_type* result = (interface_type*)(void*)this;
      return result;
    }
    
  };



Anyway, the memory layout of the TInterface object corresponds to the memory layout of the interface implementation class. So operator - > Overloading functions can be done with a rough cast. Then, when constructing TInterface object, it is forced to get the virtual function table of ImpIntIFormatble object (that is, the pointer data of its starting address) and assign the pointer value to mVtbl of InterfaceLayout. Then, it places the pointer of the actual object on mThis, and gets the type information object in mTypeInfo. If it is necessary, it also assigns the corresponding value to mParam.

And then, template < typename Interface, typename Object > struct InterfaceOf is just a variety of specialized USES, not worth mentioning.

Due to c + + abi no 1 standard system, and no c + + standard compiler must use virtual function table to implement polymorphism, so, the exotic curiosity-a solution looking here does not guarantee on all platforms are able to set up, however, non-invasive interface is convenient, is the core tools of write c + + code, 1 cut around the non-invasive interface to unfold.

Originally intended to be a long speech, but only a hasty end. , this is liberated and will leave cppblog for a long time, the contents of the plan, the message is sent, the empty template function, string, input and output, formatting, serialization, locale, global variables, template expression, combinators parser, allocator, smart Pointers, the program is running, visitors to the abstract factory pattern such as alternative implementation, in order to from a new Angle of C + + powerful, can only be interrupted.


Related articles: