C + + learning | implementation principle of polymorphism virtual destructor, pure virtual function and abstract class

1. Introduction

The basic concept of polymorphism and two simple examples based on polymorphism are introduced above. This paper will introduce the specific implementation principle of polymorphism and the use of constructor and destructor in polymorphism.

2. Implementation principle of polymorphism

The key to polymorphism is that when a virtual function is called through a base class pointer or reference, the compiler does not determine whether the called base class or the function of a derived class is determined at run time. This process is also known as dynamic binding. The realization principle of dynamic binding is the key to realize polymorphism. First, let's look at a program and its output:

class Base {
public:
	int i;
	virtual void Print() {
		cout << "Base: Print";
	}
};
class Derived :public Base {
public:
	int n;
	virtual void Print() {
		cout << "Derived: Print";
	}
};
int main() {
	Derived d;
	cout << sizeof(Base) << "," << sizeof(Derived) << endl;
	return 0;
}

The output of the final program is 8,12. However, according to relevant knowledge, sizeof(Base) should output the byte space occupied by the member variable of class Base; sizeof(Derived) should output the byte space occupied by the member variable of class Derived. The output here takes up more than four bytes, which involves the principle of polymorphic implementation.

Every class with virtual function (or the derived class of the class with virtual function) has a virtual function table, and the pointer of the virtual function table is stored in any object of the class. The virtual function address of this class is listed in the virtual function table. The extra four bytes are used to store the address of the virtual function table. The following figure shows a virtual function table:
Therefore, the implementation principle of polymorphism is: the polymorphic function call statements are compiled into a series of addresses according to the virtual function table stored in the object pointed to by the base class pointer, and then find the address of the function in the virtual function table, so as to complete the function call. Here, the address of the virtual function table is at the beginning of the whole address. Therefore, the generated virtual function table is the key to realize polymorphism.

class A {
public:
	virtual void Func() {
		cout << "A::Func" << endl;
	}
};
class B :public A {
public:
	virtual void Func() {
		cout << "B::Func" << endl;
	}
};
int main() {
	A a;	// Object a of class A
	A* pa = new B();	// Initializing pointer object pa with class B
	pa->Func();	// According to the principle of polymorphism, the pointer is initialized with class B. here, the function of class B is called
	long long* p1 = (long long*)&a;	// Forcibly convert the address of a into a long long pointer, which is 8 bytes, and then assign it to p1
	long long* p2 = (long long*)pa;	// pa is forcibly converted into a long long pointer, which is 8 bytes, and then assigned to p2
	*p2 = *p1;	//Copy the content pointed to by pointer p1 to the content pointed to by pointer p2
	pa->Func();	// Due to the function of the previous statement, the function of class A is called here
	return 0;
}

The output of the above program is B::Func '\n' A::Func. First, since p1 is eight bytes in size and points to the address of a, p1 is actually the address of the virtual function table of the base class; Similarly, the size of p2 is eight bytes, pointing to the object of the derived class. Statement * p2 = *p1 assigns the address of the virtual function table represented by p1 to p2, so at this time, the address of the virtual function table of the derived class of the pa object pointed to by p2 has changed to the address of the virtual function table of the base class. Therefore, the output of the latter item is A::Func.

3. Virtual destructor

First, when deleting a derived class object through the pointer of the base class, usually only the destructor of the base class is called. However, when deleting an object of a derived class, you should call the destructor of the derived class first, and then the destructor of the base class. We can declare the destructor as a virtual function to make the program call the destructor of the derived class at the same time. Here, if we declare the destructor of the base class as a virtual function, the derived destructor will automatically become a virtual function. At the same time, when deleting a derived class object through the pointer of the base class, first call the fictitious function of the derived class, and then call the destructor of the base class. Therefore, generally speaking, if a class defines a virtual function, its destructor should also be defined as a virtual function. Alternatively, if you use a class as a base class, you should also define its destructor as a virtual function. Note that constructors cannot be virtual functions. Let's take a program example:

class son {
public:
	~son() {
		cout << "bye from son" << endl;
	}
};
class grandson :public son {
public:
	~grandson() {
		cout << "bye from grandson" << endl;
	}
};
int main() {
	son* pson;
	pson = new grandson();
	delete pson;
	return 0;
}

The output result of the above program is bye from son, and the destructor in the derived class is not executed. When we declare the destructor of the base class as a virtual function, we will call the destructor of the base class first and then call the destructor of the base class when deleting the object.

4. Pure virtual functions and abstract classes

Virtual functions without function bodies become pure virtual functions, which we have used in the previous article. Its form is:

virtual void Print() = 0;

We make classes containing pure virtual functions abstract classes. Abstract classes can only be used as base classes to derive new classes, and objects of abstract classes cannot be created independently; Pointers and references to abstract classes can point to objects of classes derived from abstract classes. For example:

A a;		// Error, abstract class A cannot create objects independently
A* pa;		// Correctly, you can define pointers and references to abstract classes
pa = new A;	// Error, abstract class A cannot create objects independently

Note that pure virtual functions can be called in the member functions of abstract classes, that is, polymorphism; However, pure virtual functions cannot be called inside constructors or destructors. If a class derives from an abstract class, it can become a non abstract class if and only if it implements all pure virtual functions in the base class.

class A {
public:
	virtual void f() = 0;	// Pure virtual function
	void g() {	// Member functions of abstract classes call pure virtual functions
		this->f();	// Or (f);
	}
	A() {
		f();	// error
	}
};
class B :public A {
public:
	void f() {	// The derived class implements all pure virtual functions of the abstract base class. At this time, B is a non abstract class
		cout << "B::f()" << endl;
	}
};

After commenting the error part, the output result of the above program is B::f().

5. Summary

The content of this paper is a supplement to the previous article. It briefly introduces the implementation principle of polymorphism and the related content of virtual function. So far, the main content of the class has almost been briefly introduced. Later, we will introduce the file operation of C++{\rm C++}C + +, STL{\rm STL}STL, etc.

reference resources

  1. Peking University open course: programming and algorithms (III) C + + object oriented programming
finish

Tags: C++

Posted by Bullit on Wed, 25 May 2022 10:28:08 +0300