Deep understanding of C++ polymorphism

In the previous article "Analysis of Virtual Function Table-Realization of C++ Polymorphism", the realization principle of C++ polymorphism has been analyzed. This article looks at a specific example. This example is derived from a classic C++ interview question. It looks quite simple, but quite a lot of people have overturned here. This article hopes to explain this question clearly and bring you some help. The title gave the following piece of code (I slightly changed the code), asking what the output of the program is, and explaining the output result.

class A {
public:
    int a;
    virtual void x() {
        cout << "A::x()" << endl;
    }
    void y() {
        x();
        cout << "A::y()" << endl;
    }
};
class B:public A {
public:
    int b;
    virtual void x() {
        cout << "B::x()" << endl;
    }
    virtual void y() {
        x();
        cout << "B::y()" << endl;
    }
};
int main()
{
    A* p = new B;
    p->y();
    return 0;
}

You can first think about the execution result of this code for yourself, and then look at the following content.

The print result of this code is:

B::x()
A::y()

How to explain this execution result? Let's first analyze what the memory layout of the two objects A and B look like. A defines a member variable a of int type, a virtual function x(), and an ordinary member function y(). Then according to our previous analysis, we can know that the memory of the A object should contain a virtual function pointer and an int type variable a. The virtual function table pointer points to a virtual function table, and the function address of A::x() is stored in the virtual function table. Since A::y() is not a virtual function, the address of y() will not be written into the virtual function table. Calling the y() function is realized by static binding of the compiler. If you are on a 32-bit system, then an instance of an A object should occupy 8 bytes (a pointer occupies 4 bytes, and an int variable occupies 4 bytes). Its memory layout is shown in the figure:
Insert picture description here
Object B publicly inherits Object A, so there is also a virtual function table pointer and an int variable a in the memory of Object B. The content of the virtual function table in B is exactly the same as the content of the virtual function table in A. But note that because B rewrites A's x() function, then in B's virtual function table, the place where the A::x() function address was originally stored is now replaced with the B::x() function address. At the same time, because the B::y() function is also a virtual function, the function address of B::y() is appended to the end of B's ​​virtual function table. B inherits the variable a of A, and at the same time defines an int variable b. If it is on a 32-bit system, then an instance of the B object will occupy 12 bytes (a pointer, two variables of type int). The memory layout of B is as shown in the figure below:
Insert picture description here
Now back to the code, why does the print result of executing p->y() call B's x function and A's y function? In the main program, we new a B object and use an A pointer to point to it. Then the accessible range of A pointer in B's memory is as follows:
Insert picture description here
Therefore, the only functions accessible through the p pointer are B::x() and A::y(), and the only member variable accessible through p is a. When calling the p->y() function, since p is an A* pointer, the ordinary member function A::y() of A is called at this time. This function call is determined when the compiler compiles. We know that when a member function is called, the compiler will pass it an implicit parameter, this parameter is a pointer to the object where the member function is located, the value of this pointer will be assigned to the this pointer.

In this example, when p->y() is executed, the p pointer is passed to the y function and assigned to the this pointer. Calling the x() function in A::y() is equivalent to calling this->x(), calling this->x() is also calling p->x(). At this point, it is very clear. When calling p->x(), the compiler searches for the address of the x() function from the virtual function table of p to execute it. Obviously, it finds the address of B::x(). Therefore, execute the x() function of B here to print out B::x(), and then execute the y() function of A to print out A::y().

Guess you like

Origin blog.csdn.net/ww1473345713/article/details/87793220