In the book C ++ programming ideology, the implementation mechanism of virtual functions is described in detail. A general compiler inserts a piece of hidden code during compilation through a virtual function table, saves the type information and the virtual function address. During the call, the hidden code can find the virtual function implementation consistent with the actual object.
Here we provide an implementation in C that imitates the VTABLE mechanism, but everything needs to be self-assembled in the code.
I saw an article on the Internet describing the implementation of virtual functions and polymorphism in the C language. It talked about saving the pointer of the derived class in the base class and saving the pointer of the base class in the derived class to implement mutual calls, the behavior of base classes and derived classes when using virtual functions is similar to that of C ++. I think this method has great limitations, not to mention the hierarchy of inheritance, simply by saving the pointer of a derived class in the base class, it violates the intention of virtual functions and polymorphism-polymorphism is to use a derived class through the base class interface, if the base class still needs to know the information of the derived class .......
My basic ideas are:
Explicitly declare a void ** member in the "base class", save all function pointers defined by the base class as an array, and declare a member of the int type to specify the length of the void * array. The position and sequence of each function pointer defined by the "base class" in the array are fixed. This is the Convention, each required "derived class" must be filled with the array of function pointers of the base class (which may increase dynamically). If the virtual function is not rewritten, in the function implementation where the corresponding position is 0 "base class", traverse the function pointer array and find the last non-0 function pointer in the inheritance level, is the function implementation corresponding to the object actually called.
Now let's look at the Code:
struct base { void ** vtable; int vt_size; void (*func_1)(struct base *b); int (*func_2)(struct base *b, int x);};struct derived { struct base b; int i;};struct derived_2{ struct derived d; char *name;};
The above code will be discussed next. First, in C, use the function pointer in the Structure Body to correspond to the member functions in C ++. In C, all functions are inherently virtual functions (pointers can be modified at any time ).
Note that derived and derived_2 do not define func_1 and func_2. In the implementation of the virtual function in C, if the derived class needs to override the virtual function, explicit declaration is not required in the derived class. To do this, implement the function you want to rewrite in the implementation file, and fill in the rewritten function in the constructor into the virtual function table.
We are faced with a problem: the derived class does not know where the function implementation of the base class is (from the high cohesion and low coupling principle). How to initialize the virtual function table when constructing the instance of the derived class? In C ++, the compiler automatically calls constructors of all parent classes at the inheritance level, you can also call the base class constructor explicitly in the initialization list of the constructor of the derived class. What should I do?
We provide a less elegant solution:
Each class provides two functions, one constructor and one initialization function. The former generates one class, the latter is used to inherit the hierarchy and call its own classes to correctly initialize the virtual function table. Based on this principle, a derived class only needs to call the initialization function of the direct base class. Each derived class ensures this and everything can proceed.
The following are two functions to be implemented:
struct derived *new_derived();void initialize_derived(struct derived *d);
Functions starting with new are used as constructors, and those starting with initialize are used as initialization functions. Let's take a look at the implementation framework of the new_derived constructor:
struct derived *new_derived(){ struct derived * d = malloc(sizeof(struct derived)); initialize_base((struct base*)d); initialize_derived(d);/* setup or modify VTABLE */ return d;}
If it is the constructor new_derived_2 of derived_2, you only need to call initialize_derived.
After the constructor is finished, the corresponding constructor should be used, and the constructor should be a virtual function. When deleting an object, you need to call the destructor of the derived class to the top-level basic class of the hierarchy in sequence. This is also guaranteed in C. The method is to explicitly declare a destructor for the base class, find the virtual function table in the implementation of the base class, and call it from the back. The function declaration is as follows:
struct base { void ** vtable; int vt_size; void (*func_1)(struct base *b); int (*func_2)(struct base *b, int x); void (*deletor)(struct base *b);};
After constructing and analyzing the structure, we should say what is going on in the virtual function table here. Let's draw a picture first, or take the base, derived, and derived_2 just now as an example. We can see from the figure:
Assume that the derived class implements three virtual functions, and the derived_2 class implements two. func_2 is not implemented, that is, the final virtual function table owned by the instance of derived_2, and the table length (vt_size) it is 9. If it is an instance of derived, there will be no last three items in the table, and the table length (vt_size) is 6.
It must be restricted that the base class must implement all the virtual functions. Only in this way can this implementation mechanism work. This is because everything goes in from the implementation functions of the base class. By traversing the virtual function table, we can find the implementation functions of the derived class.
When we access func_1 through a base pointer (actually pointing to an instance of derived_2), func_1 implemented by the base class will find derived_2_func_1 in the vtable for calling.
Well, till now, we have basically explained the implementation principle. As for how to assemble the virtual function table and the virtual function implementation of the base class in the initialization function, we can write the code based on the above ideas. The virtual functions implemented in my method are accessed through the base class pointer, and the behavior is basically the same as that of C ++.