C++ polymorphic implementation and principle detailed analysis

  • 2020-04-02 01:42:21
  • OfStack

1. The function declared with the virtual keyword is called a virtual function, which must be a member function of the class.
2. Classes with virtual functions have a one-dimensional table of virtual functions called a virtual table. The object of the class has a virtual pointer to the beginning of the virtual table. Virtual tables correspond to classes, and virtual table Pointers to objects.
3. Polymorphism is an implementation of multiple interfaces and the core of object orientation. It is divided into class polymorphism and function polymorphism.
4. Polymorphism is realized by virtual functions, combined with dynamic binding.
5. A pure virtual function is a virtual function plus lambda is equal to 0.
An abstract class is one that contains at least one pure virtual function.

Pure virtual function: Virtual void breathe () = 0; Abstract class! You must implement this function in subclasses! First there is a name, no content, in the derived class to achieve the content!

Let's start with an example:


#include <iostream.h>
class animal
{
public:
       void sleep()
       {
              cout<<"animal sleep"<<endl;
       }
       void breathe()
       {
              cout<<"animal breathe"<<endl;
       }
};
class fish:public animal
{
public:
       void breathe()
       {
              cout<<"fish bubble"<<endl;
       }
};
void main()
{
       fish fh;
       animal *pAn=&fh; //Implicit type conversion
       pAn->breathe();
}

Note that there are no virtual functions defined in example 1-1. Consider the result of the execution of example 1-1.
The answer is animal breathe

We first define a fish object fh in the main() function, then define a pointer variable pAn to the animal class, assign the address of fh to the pointer variable pAn, and then use this variable to call pAn- > Breathe (). Many students tend to confuse this with the polymorphism of C++, believing that fh is actually an object of the fish class, which should call the fish class breathe() and print "fish bubble", but then it doesn't. Here are two reasons why.

1. Compilation perspective
When compiling c + + compiler, make sure that each object called function (this function is a virtual function) address, this is called the early binding (early binding), when we are going to fish the object of the class of fh address is assigned to the pAn, c + + compiler type conversion, the c + + compiler that variables hold pAn is the address of animal object. PAn - is executed in the main() function > When breathe(), of course, the animal object's breathe function is called.

2. Perspective of memory model
Our fish object memory model are given, as shown in the figure below: < img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201309/201309300852232.jpg ">

When we construct the object of fish class, we first call the constructor of animal class to construct the object of animal class, and then call the constructor of fish class to complete the construction of its own part, so as to splice a complete fish object. When we convert the object of the fish class to the animal type, the object is considered to be the upper half of the entire memory model of the original object, which is "the memory occupied by the object of animal" in figure 1-1. So when we use the cast object pointer to call its method, of course, it calls the method in memory. Therefore, the output animal breathe, also makes sense.

As many of you would expect, in example 1-1, we know that pAn is actually pointing to a fish object, and we want the output to be the fish's breath method, which calls the fish's breathe method. At this point, the virtual function comes into play.

The result of the previous output is that the compiler already determines the address of the function called by the object at compile time, and the late binding technique is used to solve this problem. When the compiler USES late binding, it determines the type of object and the correct function to call at runtime. To get the compiler to use late binding, you need to use the virtual keyword when declaring functions in the base class (note that this is a must, and many of the trainees have written bad examples because they didn't use virtual functions), which we call virtual functions. Once a function is declared virtual in the base class, the function is virtual in all derived classes without the need to declare it explicitly as virtual.
Now modify the code of example 1-1 to declare the breathe() function in the animal class as virtual, as follows:


#include <iostream.h>
class animal
{
public:
 void sleep()
 {
  cout<<"animal sleep"<<endl;
 }
 virtual void breathe()
 {
  cout<<"animal breathe"<<endl;
 }
};
class fish:public animal
{
public:
 void breathe()
 {
  cout<<"fish bubble"<<endl;
 }
};
void main()
{
 fish fh;
 animal *pAn=&fh; //Implicit type conversion
 pAn->breathe();
}

You can run this program again, and you'll see that the result is a "fish bubble," which calls the correct function based on the type of object.
So what happens behind the scenes when we declare breathe() as virtual?

When the compiler is compiling, it finds that there are virtual functions in the animal class. At this time, the compiler will create a virtual table (that is, vtable) for each class containing virtual functions. The table is a one-dimensional array, in which the address of each virtual function is stored. For example 1-2, both the animal and fish classes contain a virtual function breathe(), so the compiler creates a virtual table for both classes (even though there are no virtual functions in the subclass, there are in the superclass, so there are also in the subclass), as shown in the following figure:

< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201309/201309300852233.jpg ">


 

So how do you locate the virtual table? The compiler also provides a virtual table pointer (VPTR) to each class's object, which points to the virtual table of the class to which the object belongs. While the program is running, VPTR is initialized according to the type of the object, so that VPTR points correctly to the virtual table of the class to which it belongs, so that the correct function can be found when the virtual function is called. For example 1-2, since the object type pAn actually points to is fish, VPTR points to vtable of the fish class when pAn- is called > When breathe(), the fish class breathe() function is found based on the function address in the virtual table.

Because every virtual function called by an object is indexed by a virtual table pointer, proper initialization of a virtual table pointer is important. In other words, we cannot call a virtual function until the virtual table pointer is properly initialized. So when or where is the virtual table pointer initialized?

The answer is to create virtual tables and initialize virtual table Pointers in constructors. Remember the order in which constructors are called? When subclass objects are constructed, the constructor of the parent class is called first. The compiler only "sees" the parent class and does not know if there is a successor. It initializes the virtual table pointer of the parent class object, which points to the virtual table of the parent class. When a subclass's constructor is executed, the virtual table pointer to the subclass object is initialized to point to its own virtual table. For example 2-2, when the fh object of the fish class is constructed, the internal virtual table pointer is initialized to the virtual table of the fish class. After the cast, call pAn- > Breathe (), because pAn actually points to the object of the fish class, the virtual table pointer inside the object points to the virtual table of the fish class, so the fish breathe() function is finally called.

Note: For virtual function calls, each object has a virtual table pointer inside it, which is initialized as a virtual table of the class. So in the program, no matter how your object type is converted, but the object inside the virtual table pointer is fixed, so you can achieve dynamic object function call, this is the principle of C++ polymorphism.

Summary (base class has virtual functions) :
1. Each class has a virtual table.

Virtual tables can be inherited, and if a subclass does not override a virtual function, the subclass will still have the address of the function in the virtual table, except that the address refers to the virtual function implementation of the base class. If the base class has three virtual functions, then the virtual table of the base class has three (virtual function addresses), and the derived class has at least three virtual tables. If the corresponding virtual function is overridden, the address in the virtual table will change, pointing to its own virtual function implementation. If the derived class has its own virtual function, the item is added to the virtual table.

3. The virtual function addresses in the virtual table of the derived class are arranged in the same order as the virtual function addresses in the virtual table of the base class.

This is polymorphism in C++. When the C++ compiler finds that the animal class breathe() function is virtual during compilation, C++ will adopt the late binding technology. That is, the ability to determine at compile time which function is called, rather than which function is called at run time, based on the type of object (in the program, the address of the fish-like object we passed), is called polymorphism in C++. When we don't prefix the breathe() function with the virtual keyword, the C++ compiler determines which function is called at compile time, which is called early binding.

Polymorphism in C++ is achieved through late binding techniques.

Polymorphism in C++ is summed up in one sentence: add a virtual keyword to a function in a base class, override the function in a derived class, and the runtime will call the function based on the actual type of the object. If the object type is a derived class, the function of the derived class is called. If the object type is a base class, the function of the base class is called.

A virtual function is defined in a base class in order to be uncertain about the specific behavior of its derived class. Ex. :
Define a base class: class Animal// Animal. Its function is breathe()// breathe.
Define another class class Fish// Fish. It's also called breathe()
Define another class, class Sheep // Sheep. It's also called breathe()

To simplify the code, define Fish and Sheep as derived classes from the base class Animal.
However, Fish does not breathe the same way as Sheep. One is to breathe through water in the water, and the other is to breathe the air directly. So the base class isn't sure how to define breathe, so only a virtual breathe, which is an empty virtual function, is defined in the base class. The native functions are defined separately in subclasses. When a program is run, it finds a class, if it has a base class, it finds its base class, it runs a function in the base class, and then it finds a function in the base class that is a virtual identity, it goes back to the subclass and finds a function of the same name. Derived classes are also called subclasses. A base class is also called a parent class. This is the generation of virtual functions and the polymorphism of classes (breathe).

Polymorphism here refers to the polymorphism of a class.
Polymorphism is when a function is defined as a function with several different parameters, which are usually stored in the header file. When you call this function, different functions of the same name will be called for different parameters. Example: Rect () // rectangle. It can take two coordinate points (point, point) or four coordinate points (x1,y1,x2,y2). This is called polymorphism of the function and overload of the function.

Class polymorphism is implemented with virtual functions and delayed binding. A function polymorphism is an overload of a function.

In general (no virtual functions are involved), when we call a function with a pointer/reference, the function is called depending on the type of pointer/reference. If the pointer/reference is a pointer/reference to the base class object, the method of the base class is called. If the pointer/reference is a pointer/reference to a derived class object, the method of the derived class is called. Of course, if there is no such method in the derived class, the method is searched up in the base class. These calls are determined at compile time.

When polymorphism is designed with virtual functions and dynamic binding, the call is determined not at compile time but at run time. Instead of considering the type of pointer/reference separately, the function call is judged by the type of object that the pointer/reference refers to, which function is called based on the address of the function in the virtual table that the virtual pointer points to in the object.


Related articles: