Unless otherwise specified, all the following operations are performed in a 32-bit environment
Examples of subclasses used in this chapter:
class Animal { public: Animal() {}; virtual void eat() { cout << "Animal::eat()" << endl; }; virtual void bark() { cout << "bark()" << endl; }; virtual ~Animal() {}; void growUp() { age += 1; } protected: int age = 10; }; class Dog : public Animal { public: Dog() { age = 20; } void wag() {}; virtual void eat() { cout << "Dog::eat()" << " tail=" << tail << "cm" << endl; }; private: int tail = 90; };
working principle
First, verify that any class will be larger as long as it has a virtual function
Animal* a = new Animal; int* p = (int*)a; cout << sizeof(*a) << endl; cout << *p << endl; p += 1; cout << *p << endl;
The output result is
8 14588724 10
It can be seen that the object size referred to by a is 8 bytes, and directly accessing a as int * does not get the member variable i. after p + + (the address here is actually increased by 4), p points to the member variable i
At this time, a refers to the following objects:
i
age
Size is int
Size is int Viewer does not support full SVG 1.1
The above is actually the virtual function table pointer vptr, and the following is the member variable i
vptr points to the virtual function table (Vtable), which stores pointers to all virtual functions in the class, that is, each class has only one virtual function table. You can verify this
Animal a, b; cout << *(int*)(&a) << endl;// Print out the address pointed to by vptr cout << *(int*)(&b) << endl;
Output results:
4295476 4295476
The vptr of the two objects points to the same virtual function table
Take the Animal class at the beginning of this article as an example. If an object of Animal class is instantiated, the organization form of the object in memory is:
A AnimalvptrageAnimal VtableAnimal::dtor()Animal::bark()Animal::eat()
Any Animal object will have a virtual function pointer to Animal Vtable
The objects of the derived Dog class are as follows:
A DogvptragetailDog VtableDog::dtor()Animal::bark()Dog::eat()
This object also has a vptr, but it points not to the virtual function table of the Animal class, but to its own class
It should be noted that the structure of the virtual function table of the derived class is consistent with that of the base class, in which the destructor and eat() are their own, and bark() follows Animal's (the destructor compiler automatically makes one) If a virtual function is added to a derived class, it will be added to the virtual function table
If you do something evil
Point an Animal pointer to a Dog object and call the eat() function through the pointer to call Dog::eat()
Animal a; Dog b; Animal* p = &b; p->eat();
The output is as follows:
Dog::eat() tail=30cm
This is a general usage The object does not change, just let a pointer to the base class point to the object of the derived class
What happens if you assign an object of a derived class to an object of a base class?
Animal a; Dog b; a = b; a.eat();
The output is as follows:
Animal::eat()
This is called sliced off. Only the part inherited from the base class will be copied to a, and the rest will be "cut off"
Only calls through pointers or references can be dynamically bound. Here, of course, a=b; After that, even if it is called through the pointer to a, it will not be dynamically bound. This is because the virtual function table pointer vptr will not be assigned to a during the assignment operation of the object. What a calls is still a function in the Animal class
Can you do something evil? What happens if you manually assign vptr of b to a?
Never do this in actual code! This is just to study the principle of virtual function implementation
// Different compilers may be different. Here is the 32 - bit environment of cl compiler If you need to view under 64 bit, you should change 1 to 2 and 2 to 4 //I need to change * (p+1) to * (p+2) when compiling under g + +, and the original * (p+2) to * (p+3) I won't study it for the time being //If the expected results cannot be obtained, set the protected and private of Animal and Dog to public, and adjust the offset according to their addresses Animal a; Dog b; Animal* pa = &a; pa->eat();//Call Animal::eat(), which is normal usage //cout << &b.age << " " << &b.tail << endl; int* p = (int*)&a; int* q = (int*)&b; cout << "a vtable Address of" << *p << endl; cout << "age " << *(p + 1) << endl; cout << "b vtable Address of" << *q << endl; cout << "age " << *(q + 1) << endl; cout << "tail " << *(q +2) << endl; *p = *q;//Assign only Dog's vptr to a cout << "a vtable Address of" << *p << endl;//It can be observed that the vtable address of a is consistent with that of b cout << "age " << *(p + 1) << endl;//Nothing else has changed cout << "b vtable Address of" << *q << endl; cout << "age " << *(q + 1) << endl; cout << "tail " << *(q + 2) << endl; pa->eat();//Call Dog::eat()
Output results:
Animal::eat() a vtable Address 12229428 age 10 b vtable Address 12229464 age 20 tail 90 a vtable Address 12229464 age 10 b vtable Address 12229464 age 20 tail 90 Dog::eat() tail=-858993460cm
After the vptr is manually assigned, the vptr of a no longer points to the virtual function table of Animal, but to the virtual function table of Dog. Therefore, Dog::eat() will be called when eat() is called At the same time, you can see that a strange value is printed at last, because a member variable tail is added in the Dog class (you can see that although tail is private, there is no way to access or even modify it), which does not exist in the base class Animal Therefore, Dog::eat() will print the memory under a.age as a.tail
Virtual destructors
As for the destructor, if there is a virtual function in the class, the destructor of the class must also be set to virtual, otherwise there will be trouble, because if it is not virtual, the static binding occurs during the destructor, and the destructor of the derived class will be lost
Overriding
In C + +, overriding redefines the function body of the virtual function. After overriding, if you want to call the virtual function with the same name in the base class, you need to use base:: function(); Such grammar
Conditions constituting overriding:
The function names are consistent
Function parameters are consistent
The return value of the function is consistent (it is also possible if the return type has a covariant relationship, as shown in the following code)
class Expr{ public: virtual Expr* newExpr(); virtual Expr& clone(); virtual Expr self(); }; class BinaryExpr : public Expr{ public: virtual BinaryExpr* newExpr(); //OK virtual BinaryExpr& clone(); //OK virtual BinaryExpr self(); //ERROR };
Overloading and virtual functions
Overloading adds multiple signatures
class Base { public: virtual void func(); virtual void func(int); };
If you override the overloaded function in the base class, you must ensure that all overloads are overridden
- If only a part is overridden, name hiding will occur for functions with the same name in other base classes
An attempt to call virtual function through a function pointer
Now that you can get the address of the virtual function table, you naturally want to try to call it by function pointer, but it's not as simple as you think. The following content comes from my attempt. Thank you czg for your help
Configuration information of test platform: System: Windows 10 Compiler: cl (x86)/g++ (x64) If you compile under 64 bit, you need to change all 1 to 2 and 2 to 4
typedef void(*Fun)(); Animal* a = new Animal(); int* p = (int*)a;//*p is a pointer to the virtual function table int* q = (int*)*p;//The value of Q is the same as * p, pointing to the first item of the virtual function table, * q is the function pointer //Calling Animal::eat() is equivalent to ((void(*)())(*(int*)*(int*)a))(); ((Fun)*q)(); //Calling bark() is equivalent to ((void(*)())*((int*)*(int*)a+1))(); ((Fun)*(q+1))();
Output results:
Animal::eat() bark()
The function is successfully called through the function pointer. Next, try to verify the dynamic binding so that the pointer a points to an object of Dog type:
typedef void(*Fun)(); Animal* a = new Dog(); int* p = (int*)a;//*p is a pointer to the virtual function table int* q = (int*)*p;//The value of Q is the same as * p, pointing to the first item of the virtual function table, * q is the function pointer //Calling Dog::eat() is equivalent to ((void(*)())(*(int*)*(int*)a))(); ((Fun)*q)(); //Calling bark() is equivalent to ((void(*)())*((int*)*(int*)a+1))(); ((Fun)*(q+1))();
Output results:
Dog::eat() tail=-1779892224cm bark()
Dog::eat() was called successfully, but Dog::eat() failed to get the value of the member variable tail
How can I bind virtual functions to concrete objects? The natural idea is to declare the function pointer Fun as typedef void(*Fun)(Animal *);, Then pass the "this pointer" (actually a pointer to an object) to the function by passing a reference to expect the function to use this parameter like the this pointer
Then the problem arises:
typedef void(*Fun)(Animal*); Animal* a = new Dog(); int* p = (int*)a;//*p is a pointer to the virtual function table int* q = (int*)*p;//The value of Q is the same as * p, pointing to the first item of the virtual function table, * q is the function pointer ((void(*)(Animal*))(*(int*)*(int*)a))(a); //OK ((Fun)(*(int*)*(int*)a))(a); //OK ((Fun)(*(int*)*p))(a); //OK ((Fun)(*q))(a); //The value of tail is incorrect
Output result (screenshot):

However, the value of * (int*)*p) is exactly the same as * q. what's the problem?
Switch to the g + + compiler and try again:

Although the compiler gives a lot of warning, this is indeed the expected result With the help of czg students, I checked the assembly code and the Microsoft Argument Passing and Naming Conventions document
Argument Passing and Naming Conventions
https://docs.microsoft.com/en-us/cpp/cpp/argument-passing-and-naming-conventions?view=msvc-160
The following calling conventions are supported by the Visual C/C++ compiler.
Keyword | Stack cleanup | Parameter passing |
---|---|---|
__cdecl | Caller | Pushes parameters on the stack, in reverse order (right to left) |
__clrcall | n/a | Load parameters onto CLR expression stack in order (left to right). |
__stdcall | Callee | Pushes parameters on the stack, in reverse order (right to left) |
__fastcall | Callee | Stored in registers, then pushed on stack |
__thiscall | Callee | Pushed on stack; this pointer stored in ECX |
__vectorcall | Callee | Stored in registers, then pushed on stack in reverse order (right to left) |
When a member function is called __thiscall , the this pointer is stored in the ECX register through parameter transfer __cdecl The parameter is pushed into the stack. Therefore, the problem here is that the member function Dog::eat() wants to get the this pointer from the ECX register, but this is not there, so the obtained tail value is wrong
As for why the first few seem to work normally, it is because the value of a is just move d into the ECX register during function execution
You can take a look at the corresponding assembly code

This situation can be repeated under Visual Studio x86 compilation, but it has not occurred in g + + compilation This matter is related to different platforms and compilers, so you only need to understand the principle of virtual function polymorphism, and you don't have to implement it in code