[C++] polymorphism, virtual function table related problems

The concept of polymorphism and its triggering conditions

  The concept of polymorphism: Generally speaking, it is a variety of forms. The specific point is to complete a certain behavior. When different objects complete it, different

conditions for state polymorphism will be generated:
1. The virtual function must be called through the pointer or reference of the base class (that is, the class member modified by virtual The function is called a virtual function)
2. The called function must be a virtual function, and the derived class must rewrite the virtual function of the base class

Overriding and Covariation

  Rewriting (covering) of virtual functions: There is a virtual function in the derived class that is exactly the same as the base class (that is, the return value type, function name, and parameter type of the virtual function of the derived class and the virtual function of the base class), called the virtual function of the subclass. The function overrides the virtual function of the base class. There

are two exceptions to the virtual function override:
1. Covariance (the return type of the virtual function of the base class is different
  from that of the derived class ). The return value types are different. That is, when the base class virtual function returns the pointer or reference of the base class object, and the derived class virtual function returns the pointer or reference of the derived class object, it is called covariance 2. Rewriting of the destructor
(the base class and the derived class destructor Names are different)
  If the destructor of the base class is a virtual function, as long as the destructor of the derived class is defined at this time, no matter whether the virtual keyword is added, it will be rewritten with the destructor of the base class. The constructor names are different. Although the function names are different, it seems to violate the rewriting rules, but it is not the case. It can be understood here that the compiler has made special treatment for the name of the destructor. After compilation, the name of the destructor is uniformly processed into destructor

override and final. keywords

(Test point 1)

It is emphasized here that rewriting and rewriting is the implementation. Look at the following scene: (test point)

class A
{
    
    
public:
	A()
	{
    
    }
	virtual void func(int val = 1) 
	{
    
    
		std::cout << "A->" << val << std::endl; 
	}

	virtual void test() 
	{
    
    
		func(); 
	}
};
class B : public A
{
    
    
public:
	void func(int val = 0) 
	{
    
     
		std::cout << "B->" << val << std::endl; 
	}
};
int main()
{
    
    

	A* p = new B();
	p->test();
	return 0;
}

  The printed result is B->1, indicating that the func function of the subclass is called, but the default value is the parent class. The return value, function name, and parameter types are the same, which constitutes rewriting, and rewriting is the implementation , the shell uses the parent class, and the written content is controlled by yourself

(Test point 2)

So why should the destructor be rewritten? Look at the following scene: (test point)


class Person {
    
    
public:
	 ~Person() {
    
     cout << "~Person()" << endl; }
};
class Student : public Person {
    
    
public:
	~Student() {
    
    
		cout << "~Student()" << endl;
		delete[] ptr;
	}
protected:
	int* ptr = new int[10];
};

int main()
{
    
    
	Person* p = new Person;
	delete p;

	p = new Student;
	delete p; 
	return 0;
}

  When we use a parent class pointer to point to a subclass object, we expect the subclass object to be destructed, not the parent class object. If it does not constitute rewriting, no matter whether the parent class pointer points to the subclass object or the parent class object, the destructor is the parent class object, resulting in the following ptr dynamically opening up the space and memory leaks. When we give the parent class





  destructor Add virtual to make it rewritten. At the same time, note that after the destructuring play~Student will be destructed and inherited from the parent class, echoing the above structure first father and then son, "destruction first son and then father "



Abstract class:
  Write =0 after the virtual function, then this function is a pure virtual function. Classes containing pure virtual functions are called abstract classes (also called interface classes), and abstract classes cannot instantiate objects. Objects cannot be instantiated after the derived class inherits. Only by rewriting the pure virtual function can the derived class instantiate the object. The pure virtual function specifies that the derived class must be rewritten, and the pure virtual function also reflects the interface inheritance




dynamic binding and static binding:

Virtual function table and its location

  A class containing a virtual function has at least one virtual function table pointer, because the address of the virtual function must be placed in the virtual function table, and the virtual function table is also referred to as a virtual table. Let’s look at the object model through the following example



(Test point 3)

At this time, let's think in reverse, why must it be a pointer or reference of the parent class instead of an object of the parent class?

  First of all, we can see that the subclass object will first copy the virtual function table of the parent class, and then modify the address of the virtual function that needs to be rewritten.
  If we assign the subclass object to the parent class object, should the virtual function table of the subclass object be copied to the parent class? If the virtual function table is not copied, then the function of the parent class is still called, which does not constitute polymorphism.
  If it is copied, then the virtual function table of the parent class object stores the modified virtual function of the subclass object, as shown in the figure below: At this time, we can no longer call the rewritten function of the parent class itself, because no matter whether we pass the subclass or the parent Class objects call functions of subclass objects and cannot form polymorphism.
  Therefore, the condition of polymorphism must be a pointer or reference of the parent class, which can avoid errors caused by copying like the following.

vtable location

class Person {
    
    
public:
	virtual	void BuyTicket() const {
    
     cout << "成人-全价" << endl; }
};
class Student : public Person {
    
    
public:
	virtual void BuyTicket() const {
    
     cout << "学生-半价" << endl; }
};
int main()
{
    
    
	Person ps;
	Student st;
	int a = 0;
	printf("栈:%p\n\n", &a);
	static int b = 0;
	printf("静态区:%p\n\n", &b);
	int* p = new int;
	printf("堆:%p\n\n", p);
	const char* str = "hello world";
	printf("常量区:%p\n\n", str);
	printf("虚表1:%p\n", *((int*)&ps));
	printf("虚表2:%p\n", *((int*)&st));
	return 0;
}

  Where is the virtual table stored? First exclude the heap, the virtual table is generated by the compiler, and will not dynamically apply for space by itself. Secondly, the stack is excluded. Objects of the same type share a virtual table. The stack goes along with the stack frame. If the function call ends, the stack frame is destroyed, and the virtual table is destroyed. Let's use the printing method to see where the virtual table exists.
  Looking at the following code and output results, we can find that the virtual table exists in the constant area.

Virtual function table in multiple inheritance

// 打印函数指针数组
typedef void(*FUNC_PTR) ();
void PrintVFT(FUNC_PTR* table)
{
    
    
	for (size_t i = 0; table[i] != nullptr; i++)
	{
    
    
		printf("[%d]:%p->", i, table[i]);
		FUNC_PTR f = table[i];
		f();//这个地址可以调用说明一定是函数
	}
	printf("\n");
}

class Base1 {
    
    
public:
	virtual void func1() {
    
     cout << "Base1::func1" << endl; }
	virtual void func2() {
    
     cout << "Base1::func2" << endl; }
private:
	int b1;
};
class Base2 {
    
    
public:
	virtual void func1() {
    
     cout << "Base2::func1" << endl; }
	virtual void func2() {
    
     cout << "Base2::func2" << endl; }
private:
	int b2;
};

class Derive : public Base1, public Base2 {
    
    
public:
	virtual void func1() {
    
     cout << "Derive::func1" << endl; }
	virtual void func3() {
    
     cout << "Derive::func3" << endl; }
private:
	int d1;
};

int main()
{
    
    
	Derive d;
	int vft1 = *((int*)&d);
	Base2* ptr = &d;
	int vft2 = *((int*)ptr);
	printf("第一张虚表:\n");
	PrintVFT((FUNC_PTR*)vft1);
	printf("第二张虚表:\n");
	PrintVFT((FUNC_PTR*)vft2);
	return 0;
}

  First look at the above code, how many virtual tables does the d object have? Looking at the monitoring window below, it is obvious that object d has two virtual tables, but where is the virtual function func3 of object d? In fact, it is in the first virtual table. We can print and observe it through the above code . () This address can be called, indicating that it must be a function. Here it can be considered that the monitor window of the compiler deliberately hides the func3 function, or it can be considered a small bug of it



  But if you look more carefully, you will find that the addresses of func1 in the two tables are different. Don’t they both rewrite the func1 function? And if you use the parent class pointer to call, you will find that they call the same function, so why are the addresses different here?



Look at the scene below


  
and note: what you want to call here is the func1 function of the derived class d object, the this pointer should point to the d object, and the ptr1 pointer here just points to the d object, no need to change. But ptr2 points to the Base2 object. Calling the func1 function of the d object should pass the this pointer of the d object, not the this pointer of the Base2 object. So the address of the second table here is actually a "virtual address", and a few more layers are encapsulated to correct the this pointer

Next, let's take a look at the difference between ptr1 and ptr2 calls through assembly, and better understand the "virtual address" of Base2.
ptr1 calls




ptr2 calls

Guess you like

Origin blog.csdn.net/Front123456/article/details/131955759