Implementation Mechanism of Polymorphism (I) -- C ++

Source: Internet
Author: User
Polymorphism (Polymorphism) is the core concept of object-oriented. This article takes C ++ as an example to discuss the implementation of Polymorphism. Polymorphism in C ++ can be divided into dynamic polymorphism Based on inheritance and virtual functions and static polymorphism Based on templates. If not specified, the polymorphism mentioned in this Article refers to the former, that is, dynamic polymorphism Based on inheritance and virtual functions. If you are interested in what is polymorphism, how to use polymorphism in object orientation, and the benefits of using polymorphism, you can refer to this object-oriented book.
For convenience, the following is a simple example of using polymorphism (From [1]): class Shape
{
Protected:
Int m_x; // X coordinate
Int m_y; // Y coordinate
Public:
// Pure virtual function for drawing
Virtual void Draw () = 0;

// A regular virtual function
Virtual void MoveTo (int newX, int newY );

// Regular method, not overridable.
Void Erase ();

// Constructor for Shape
Shape (int x, int y );

// Virtual destructor for Shape
Virtual ~ Shape ();
}; // Circle class declaration
Class Circle: public Shape
{
Private:
Int m_radius; // Radius of the circle
Public:
// Override to draw a circle
Virtual void Draw ();

// Constructor for Circle
Circle (int x, int y, int radius );

// Destructor for Circle
Virtual ~ Circle ();
}; // Shape constructor implementation
Shape: Shape (int x, int y)
{
M_x = x;
M_y = y;
}
// Shape destructor implementation
Shape ::~ Shape ()
{
//...
} // Circle constructor implementation
Circle: Circle (int x, int y, int radius): Shape (x, y)
{
M_radius = radius;
}

// Circle destructor implementation
Circle ::~ Circle ()
{
//...
}

// Circle override of the pure virtual Draw method.
Void Circle: Draw ()
{
Glib_draw_circle (m_x, m_y, m_radius );
} Main ()
{
// Define a circle with a center at (50,100) and a radius of 25
Shape * pShape = new Circle (50,100, 25 );

// Define a circle with a center at (5, 5) and a radius of 2
Circle aCircle (5, 5, 2 );

// Various operations on a Circle via a Shape pointer
//Polymorphism
PShape-> Draw ();
PShape-& gt; MoveTo (100,100 );

PShape-> Erase ();
Delete pShape;

// Invoking the Draw method directly
ACircle. Draw ();
}

The polymorphism code used in the example is highlighted in the black body. One of their obvious features is to call methods of different subclasses through a base class pointer (or reference.
Now the question is, how is this function implemented? Let's take a rough guess: when the level of assembly code is reached for general method calls, it is generally called using commands such as Call funcaddr, funcaddr is the address of the function to be called. In theory, when I use the pointer pShape to Call Draw, the compiler should assign the address of Shape: Draw to funcaddr, and then Call the command to directly Call Shape: Draw, this is the same as calling Shape: Erase with pShape. However, the running result tells us that the value that the compiler assigns to funcaddr is the value of Circle: Drawde. This shows that the compiler uses dual standards for the Draw and Erase methods. So who has such a high level of power that makes the compiler A non-stop judge look at it differently?Virtual !!
Clever !! This is exactly the keyword "virtual. Here, we need to clarify two concepts: static binding and dynamic binding.
1. static binding (static bingding) is also called early binding. In short, the compiler clearly knows the method to be called during compilation, the address of the method is assigned to the funcaddr of the Call Command. Therefore, you can Call the corresponding method by directly using the Call Command during running.
2. dynamic binding, also known as late binding, is different from static binding. during compilation, the compiler does not know exactly which method to call, you need to know which object is used during the running to decide.
Well, with these two concepts, we can say that the role of virtual is to tell the compiler: I want to dynamically bind it! Of course, the compiler will respect your opinions. In order to fulfill your requirements, the compiler will do a lot of things: the compiler automatically inserts a pointer vptr and a data structure VTable into the class that declares the virtual method (vptr is used to point to VTable; VTable is a pointer array with the address of the function stored in it ), and ensure that the two comply with the following rules:
1. VTable can only store methods declared as virtual, and other methods cannot be stored in it. In the preceding example, only Draw, MoveTo, and ~ are supported in the Shape VTable ~ Shape. The address of method Erase cannot be stored in VTable. In addition, if the method is a pure virtual function, such as Draw, it is also necessary to retain the corresponding position in the VTable, but because the pure virtual function does not have a function body, therefore, this location does not store the Draw address. Instead, you can choose to store the address of an error processing function. When this location is accidentally called, you can use the error function for corresponding processing.
2. The index number of the virtual function address inherited from the base class in the VTalbe of the derived class must be consistent with the index number of the virtual function in the base class VTable. For example, in the preceding example, In the Shape VTalbe, Draw is set to 1, MoveTo 2 ,~ Shape is 3, no matter which sequence these methods are defined in the Circle, Draw must be set to 1 in the VTable of the Circle, and MoveTo must be set to 2. As for number 3, here is ~ Circle. Why not ~ Shape? Hey, forget, destructor won't inherit.
3. vptr is generated automatically by the compiler, so the compiler must initialize it. The initialization time is selected when the object is created, and the location is in the constructor. Therefore, the compiler must ensure that each class has at least one constructor. If not, it automatically generates a default constructor.
4. vptr is usually placed at the beginning of the object, that is, Addr (obj) = Addr (obj. vptr ).
You see, there is really no free lunch in the world. To achieve dynamic binding, the compiler has to do so many dirty words for us silently. If you want to experience the hard work of the compiler, you can try to simulate the above behavior in C language. [1] has such an example. Now everything is ready. Compile, connect, load, GO! When the program runsPShape-> Draw ()The above facilities also started to work ..
As mentioned above, the reason why late binding cannot determine which function to call is because the specific object is not sure. Okay, when runningPShape-> Draw ()When the object comes out, it is marked by the pShape pointer. After finding this object, we can find the vptr in it (at the beginning of the object). With the vptr, we can find the VTable, the called function is in sight .. So many VTable methods. Which one do I use? Don't worry, the compiler has already made a record for us: when creating a VTable, the compiler has arranged a seat number for each virtual function and recorded this index number. Therefore, when the compiler parsesPShape-> Draw ()It has quietly replaced the function name with an index number. At this time, we can use this index number to get a function address in VTable, Call it!
Here, we can see why there is a second rule. Generally, we use the base class pointer to reference the object of the derived class, But no matter which derived class the specific object belongs, we can all use the same index number to obtain the corresponding function implementation.
In reality, there is an example like this: the alarm number is 110,119,120 (different methods in VTable ). The results of dialing different numbers from different places are different. For example, if a person (specific object) outside the third ring road hits 119 with a person in the third ring road (another specific object), the fire brigade called at last is definitely different. This is polymorphism. How is this implemented? Everyone knows an alarm center (VTable, which contains three methods: 110,119,120 ). If a person outside the third ring road needs a fire emergency (a specific object), he will call 119, but he certainly does not know which fire brigade will appear. This is determined by the alarm center. The alarm center uses this specific object (in this example, the specific location) and the phone number he calls (which can be understood as an index number ), the alarm center can determine which fire brigade should be scheduled for rescue (different actions ).
In this way, with the help of vptr and VTable, we implement dynamic binding of C ++. Of course, this is only the case of single inheritance. The process of multi-inheritance is more complex. The following briefly describes the simplest situation of Multi-inheritance. As for the situation of virtual inheritance, if you are interested, you can check out Lippman's "Inside the C ++ Object Model", which will not be expanded yet. (I haven't figured it out yet. Besides, I don't know how to use multiple inheritance, so there are even fewer opportunities for the virtual inheritance application)
First, let's talk about the memory layout of objects under multi-inheritance, that is, how the object stores its own data.

Class Cute
{
Public:
Int I;
Virtual void cute () {cout <"Cute cute" <endl ;}
}; Class Pet
{
Public:
Int j;
Virtual void say () {cout <"Pet say" <endl ;}
}; Class Dog: public Cute, public Pet
{
Public:
Int z;
Void cute () {cout <"Dog cute" <endl ;}
Void say () {cout <"Dog say" <endl ;}
};

In the above example, the layout of a Dog object in the memory is as follows:

Dog

Vptr1

Cute: I

Vptr2

Pet: j

Dog: z

That is to say, there will be two vptr in the Dog object, each corresponding to the inherited parent class. If we want to implement polymorphism, we must find the corresponding vptr in the object accurately to call different methods. However, if the logic for a single inheritance, that is, the vptr is placed at the starting position of the pointer, it must be implemented in the case of multiple inheritance, we must ensure that when the pointer of a derived class is implicitly or explicitly converted to a parent class pointer, the result is directed to the starting position of the corresponding derived class data in the Dog object. Fortunately, the compiler has already helped us. In the above example, if the Dog is converted to Pet, the compiler automatically calculates the offset of the Pet data in the Dog object. The offset is added to the start position of the Dog object, the actual address of the Pet data. Int main ()
{
Dog * d = new Dog ();
Cout <"Dog object addr:" <d <endl;
Cute * c = d;
Cout <"Cute type addr:" <c <endl;
Pet * p = d;
Cout <"Pet type addr:" <p <endl;
Delete d;
} Output:
Dog object addr: 0x3d24b0
Cute type addr: 0x3d24b0
Pet type addr: 0x3d24b8 // point to the vptr2 of the Dog object, that is, Pet data.

Well, since the compiler automatically converts the addresses of different parent classes, the process of calling the virtual function is unified by documentary inheritance: through specific objects, find vptr (usually the starting position of the pointer, so Cute finds vptr1, while Pet finds vptr2). Through vptr, we find VTable, then, based on the VTable index number obtained during compilation, we get the corresponding function address and then we can call it immediately.

Here, by the way, we also mention the special characteristics of the two special methods in polymorphism: the first is the constructor, and calling a virtual function in the constructor will not have polymorphism, example:

Class Pet
{
Public:
Pet () {sayHello ();}
Void say () {sayHello ();}

Virtual void sayHello ()
{
Cout <"Pet sayHello" <endl;
}

}; Class Dog: public Pet
{
Public:
Dog (){};
Void sayHello ()
{
Cout <"Dog sayHello" <endl;
}
}; Int main ()
{
Pet * p = new Dog ();
P-> sayHello ();
Delete p;
} Output:
Pet sayHello // The sayHello () of Pet is called directly ()
Dog sayHello // Polymorphism

The second is the destructor. When using polymorphism, we often use the base class pointer to reference the object of the derived class. If it is dynamically created, after the object is used up, delete is used to release objects. However, if we do not pay attention to it, unexpected situations may occur.

Class Pet
{
Public:
~ Pet () {cout <"Pet destructor" <endl ;}
// Virtual ~ Pet () {cout <"Pet virtual destructor" <endl ;}
}; Class Dog: public Pet
{
Public:
~ Dog () {cout <"Dog destructor" <endl ;};
// Virtual ~ Dog () {cout <"Dog virtual destructor" <endl ;}
}; Int main ()
{
Pet * p = new Dog ();
Delete p;
} Output:
Pet destructor // bad. Dog's destructor are not called, memory leak!

If we change the destructor to virtual, the result is as follows:
Dog virtual destructor
Pet virtual destructor // That's OK!

Therefore, if a class is designed to be inherited, its destructor should be declared as virtual.

Reference:
[1] Comparing C ++ and C (Inheritance and Virtual Functions)
[2] exploration of c ++ object layout and polymorphism implementation
[3] Multiple inheritance and the this pointer describes the type conversion problem under multi-inheritance

[4] Memory Layout for Multiple and Virtual Inheritance describes the object Memory Layout and type conversion under Multiple diamond multi-Inheritance.

Transfer http://hi.baidu.com/daping_zhang/blog/item/e87163d06c42818fa0ec9cfc.html

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.