Polymorphism is an advanced feature of object-oriented language. Whether it is the bottom-level implementation or the overall architecture design, polymorphic thinking has a wide range of applications. Learning polymorphism is not only to learn a programming technique, but also to master the design idea behind it. This article from the bottom of the story, 1.1 points to analyze the context of polymorphism, hope to give you a true polymorphism.
From the virtual function.
Virtual function is the language foundation of multi-state implementation, and we implement polymorphic technology by declaring virtual function in the embodiment of inheritance. There are mainly three key points here:
① inheritance embodies, polymorphism must exist in an inheritance embodiment, no inheritance will not have polymorphic.
② virtual functions, only member functions declared as virtual can produce polymorphic effects.
The ③ base class pointer or reference points to the derived class object, which is the last condition of the polymorphic implementation, and the compiler dynamically determines which virtual function to invoke based on the type of the derived class object, and the polymorphism is generated.
A simple Polymorphic sample code is as follows:
#include <iostream>using namespace Std;class b{//base class Private:int m_n;double M_d;public:int M_f ();//normal function virtual int M_VF ();//virtual function Virtual ~b () {}//virtual destructor};int b::m_f () {cout<< "B::m_f () ..." <<endl;return 0;} int B::M_VF () {cout<< "B::M_VF () ..." <<endl;return 0;} Class D:public b{//derived class Public:int M_f ();//normal function virtual int m_vf ();//virtual function ~d () {}//virtual destructor};int d::m_f () {cout<< "D::m_f () ... "<<endl;return 0;} int D::M_VF () {cout<< "D::M_VF () ..." <<endl;return 0;} void Testfun (b *pb) {Pb->m_f ();//Call General function PB->M_VF ();//Call virtual function}int Main () {//virtual function test B b;d d;testfun (&b); Testfun ( &D); system ("pause"); return 0;}
It has been tested to find that when a member function is called with a pointer, if the function is a normal function, the version of the calling function is determined based on the type of the pointer, that is, the base class pointer can only call functions in the base class. If the function is a virtual function, the version of the calling function is determined based on the type of the pointer, and if the object being pointed to is a derived class object, the version of the function in the derived class is called.
So how does the compiler determine function calls based on the type? This should start with the underlying implementation of the virtual function.
The underlying implementation of virtual function
This topic is relatively large, involving the object model in C + +, from the general member function is how to implement the speaking. When we define a class in the program, the C + + compiler does not allocate any memory for us. The object is allocated memory only when the class is instantiated as an object, and only the data members of the class are allocated memory, and the member functions do not appear in the block of memory that the object occupies. When these functions are called, the compiler automatically adds the this pointer to the function and accesses the corresponding class object through the this pointer. That is, the member function has only one copy of the implementation code in the entire class. Corresponds to the following:
However, if we change a function in a class to a virtual function, the compiler will do the following to implement the polymorphic mechanism:
① sets a pointer table for a class that contains virtual functions. This table is what we call the virtual function table (virtual table), the virtual function table and the class containing the virtual function is one by one corresponding. Whenever we define a class that contains a virtual function table, a virtual function table is generated. The contents of the table are the entry addresses of all virtual functions in this class, that is, function pointers.
② appends a pointer to the memory block of each class object, which points to the virtual function table of its class, which is our virtual table pointer (vptr).
Following these two changes, the current object model becomes as shown:
Here are the following two facts: First, the parent and derived classes correspond to different virtual function tables, and the different derived classes of the uniform inheritance hierarchy also correspond to different virtual function tables. Or that sentence, the virtual function table and the class type is one by one corresponding. The second is that although the virtual function table is different, the position of the virtual table pointer in the object is indeed relatively fixed, that is, the object of all classes (in the same inheritance embodiment) is stored with the virtual table pointer at the same offset of the memory block (assuming an offset of 0 in the figure).
With these two facts, we can speculate on how the compiler did it when implementing polymorphism. Let's go back to the code at the beginning of this article. When the Testfun () function is called, the parameter PB pointer points to the argument object. When using PB to call M_VF (), the compiler found that this is a virtual function, so the compiler found a virtual table pointer based on PB, and then based on this pointer to find the virtual function table, in the virtual function list to find the name similar to POINTER_TO_M_VF () function pointer, and then through this pointer to find the corresponding M_VF () code, which enables polymorphic calls.
Through the above analysis, we find that virtual table pointers are the key to realizing polymorphism. It is because the object has such a parameter according to the type change, the polymorphic call can proceed smoothly. Moreover, if we omit the middle virtual function table step, or if we have only one virtual function, then the virtual function table can be omitted. In this way, polymorphism becomes one thing: add a pointer to an object, use it to point to the function we want to call, and then pass the object with the pointer to a procedure, and the procedure calls the previous function. This reflective invocation is the use value of a function pointer, which, in some ways, is a more basic concept than polymorphism.
function pointers
Polymorphism is implemented by means of function pointers, and the use of function pointers enables more flexible functions than polymorphism. As already mentioned, a virtual table pointer is a compiler that is automatically added to an object based on its type, so that all objects of a class have the same virtual table pointer, and there is a corresponding virtual function table. This final effect is to invoke the same set of virtual functions for the same class of objects. Suppose we ourselves add a function pointer to the object and initialize the pointer in the class's constructor. This allows us to specify different "virtual functions" for each specific object. The simple test cases are as follows:
#include <iostream>using namespace Std;int foo1 () {cout<< "foo1 () ..." <<endl;return 0;} int Foo2 () {cout<< "Foo2 () ..." <<endl;return 0;} Class A{private:int (*pfun) (); int m_n;double m_d;public:a (int (*p_fun) ()): Pfun (p_fun) {}//initial function pointer friend void Show (A *pa) ;}; void Show (A *pa) {pa->pfun ();} the int main () {///function pointer tests a A1 (FOO1),//A1 's function pointer points to foo1a A2 (foo2),//a2 function pointer to Foo2show (&A1), Show (&A2), System ("Pause" ); return 0;}
The test results show that objects of the same type do call different functions in the pa->pfun () process, and the whole process does not use virtual functions! This is actually the artificial implementation of the polymorphic mechanism, and more flexible than the virtual function implementation of polymorphism, in the actual use of the process, we can not only transfer functions, but also transfer function objects, member function pointers and so on. Of course, this method is more complicated than the virtual function method, and the newly added friend function destroys the encapsulation of the class. Whether this mechanism should be adopted or not should be analyzed according to the specific situation.
If you are familiar with the design pattern, you should be able to see that this implementation strategy is actually a simple application of the strategy design pattern. In fact, it is because of the introduction of function pointer or polymorphism, which lays the language foundation for the programming pattern design.
Design Patterns
About the content of design patterns enough to write a book, in fact there are a lot of such books, interested students can buy a book to do a good study. This illustrates the important role of polymorphism in pattern design.
One of the important concepts of software design pattern is the open and close principle, that is, the program requires the design of the extension opening, the modification is closed. Frankly speaking, when we want to add new features, just add some code, without modifying the original code. The benefits of doing this are obvious and the key is how to achieve the problem. In object-oriented languages, this design is done using the concept of an interface. An interface is a place where programs are set aside to add or change functionality. The traditional program is executed according to the control flow step-by-step sequence, each module is called to complete a certain function in turn. To change the functionality of a module, you must go inside the module for code modification. And the appearance of the interface for the design of a new idea, we can implement the new module according to the interface, and then these modules can be directly attached to the original program. Here's a note about this:
In a diagram with an interface, Module 2 calls different versions of Module 3 depending on the situation. In other words, Module 2 reserved The interface, as long as Module 3 implements this interface, then it can be called by Module 2. In language, this interface is usually implemented with virtual functions, where we can consider module 2 as a base class, which defines a virtual function, and then module 3 is all derived classes of Module 2, they override the virtual function in the base class, and then the compiler can invoke different functions according to different module 3 type objects. Say so much, or give an example to help you understand, here is a template pattern implemented by the calculation of different functions run time program.
#include <iostream> #include <time.h> #include <windows.h> #include <stdlib.h>using namespace Std;class timecounter{private:time_t start;time_t end;virtual void runfun () = 0;//Interface Public:timecounter (): Start (0), end (0) {}void caltime () {//Calculate call time start = times (null); Runfun ();//call interface function end = Duration (null);} time_t GetTime () {return end-start;}}; Class funcal1:public timecounter{private:virtual void Runfun () {//Rewrite interface sleep (2000);}}; Class funcal2:public timecounter{private:virtual void Runfun () {//rewrite interface sleep (5000);}}; int main () {//Template method mode test FunCal1 FC1; FunCal2 fc2;fc1.caltime (); Cout<<fc1.gettime () <<endl;fc2.caltime (); Cout<<fc2.gettime () << Endl;system ("pause"); return 0;}
As can be seen from the code, Timecounter acts as an interface class, so long as a class inherits it and implements the Runfun () interface, it is possible to compute the time of different functions in a uniform way.
Such design patterns abound in the programming system, and almost all of the third-party libraries we are familiar with are connected to the system. For the most common example, the database is almost an integral part of every program system, and the database manufacturers have a lot of, we can conclude that Oracle's operation of the data table and DB2 is not the same, then we are programmed to learn how each database operation method? Of course not, all software development tools, whether Microsoft's C # or Sun's Java, provide database operation specifications for database operations. For example, to get a connection, execute a query statement, and so on, as long as the database manufacturers in accordance with this specification to develop their own methods, the user can be used uniformly without the implementation of the details of the tube. This specification is actually software development tools left to the database manufacturers interface.
The discussion of polymorphism has come to an extent, and in many languages, pointers have become reserved content. The reason we want to start with pointers is because the indirection of pointers is a very valuable feature in programming, and it is this indirection that isolates the coupling between different modules, which is the foundation of many advanced features and pattern designs.
finally: The multi-state thought is broad and profound, plus my ability is limited, as the text have a the wrong place, welcome everybody to point out!
C + + polymorphism: from virtual table pointers to design patterns