Virtual function, pure virtual function, polymorphism

1. Virtual function

        Add the virtual keyword before the function of the base class and rewrite the function in the derived class. The corresponding function will be called according to the actual type of the pointed object at runtime. If the object type is a derived class, the function of the derived class will be called. Function, if the object type is a base class, calls the function of the base class.

(1) Virtual table and virtual base table pointers

To understand this problem, we have to introduce virtual tables and virtual base tables

Virtual table: the abbreviation of virtual function table. When a class contains a method modified by the virtual keyword, the compiler will automatically generate a virtual table, which is determined by the compiler

Virtual table pointer: When a class containing a virtual function instantiates an object, the first four bytes of the object address store a pointer to the virtual table, which is initialized in the constructor.

(2) Pure virtual function

Pure Virtual Function is a special type of virtual function in C++ that is declared but not defined in the base class. A pure virtual function is declared using virtualthe keyword, added at the end of the function declaration = 0to indicate that it is a pure virtual function. Subclasses (derived classes) must provide actual implementations of pure virtual functions, otherwise the subclass will also be marked as an abstract class and cannot create objects.

class Shape {
public:
    // 声明纯虚函数
    virtual void draw() = 0;

    // 普通成员函数
    void displayInfo() {
        // 这里可以包含一些通用的代码
        std::cout << "This is a shape." << std::endl;
    }
};

class Circle : public Shape {
public:
    // 子类必须提供纯虚函数的实现
    void draw() override {
        std::cout << "Drawing a circle." << std::endl;
    }
};

class Square : public Shape {
public:
    // 子类必须提供纯虚函数的实现
    void draw() override {
        std::cout << "Drawing a square." << std::endl;
    }
};

int main() {
    Circle circle;
    Square square;

    circle.displayInfo(); // 调用基类函数
    circle.draw();        // 调用派生类函数

    square.displayInfo(); // 调用基类函数
    square.draw();        // 调用派生类函数

    return 0;
}

In the above example, Shapethe class contains a pure virtual function draw(), so Shapethe class itself is an abstract class and its objects cannot be created . Then, both the Circleand Squareclass inherit from Shapeclass and must provide draw()the actual implementation of the pair. This mechanism allows the implementation of polymorphism, allowing different derived classes to implement the same virtual function in different ways.

2. Implementation of polymorphism

Analyze based on the example above:

#include<iostream>
#include<vector>
using namespace std;
class A {
public:
	virtual void prints() {
		cout << "A::prints" << endl;
	}
	A() {
		cout << "A:构造函数" << endl;
	}
};
class B:public A {
public:
	virtual void prints() {
		cout << "B::prints" << endl;
	}
	B() {
		cout << "B:构造函数" << endl;
	}
};
class C :public A {
public:

};
int main() {
	A *b = new B();
	b->prints();
	b = new C();
	b->prints();
	return 0;
}

 Subclass B rewrites the virtual function of base class A, but subclass C does not rewrite it. From the result analysis, it still reflects polymorphism? ? ?

The process of implementing polymorphism is explained below:

1. When the compiler discovers that a base class contains a virtual function, it will automatically generate a virtual table for each class containing a virtual function. The table is a one-dimensional array, and the virtual table stores the entry address of the virtual function.

2. The compiler will save a virtual table pointer, vptr, in the first four bytes of each object, pointing to the virtual table of the class to which the object belongs.

3. The so-called appropriate time, when the derived class defines an object, the program will automatically call the constructor, create a virtual table in the constructor and initialize the virtual table pointer. When constructing a subclass object, the constructor of the parent class will be called first. At this time, the compiler only "sees" the parent class and initializes the vtable pointer for the parent class object so that it points to the parent class's vtable; when calling When constructing the subclass, initialize the virtual table pointer for the subclass object so that it points to the virtual table of the subclass.

4. When the derived class does not override the virtual function of the base class, the virtual table pointer of the derived class points to the virtual table of the base class; when the derived class overrides the virtual function of the base class, the virtual table pointer of the derived class points to the virtual table of the base class. Points to its own virtual table; when the derived class has its own virtual function, add the virtual function address at the end of its own virtual table.

Therefore, at runtime, the base class pointer pointing to the derived class can be dynamically called based on the rewriting of virtual functions by the derived class, thus achieving polymorphism. 

3. Why are destructors generally written as virtual functions?

        Due to the polymorphism of classes, subclass objects are usually manipulated through parent class pointers or references. Because multiple sets allow us to handle different derived class objects in a unified way and determine at runtime which methods to call.

        If the destructor is not declared as a virtual function, the compiler implements static binding. When the base class pointer is deleted, only the base class destructor will be called instead of the derived class destructor, which will cause the derived class to be destructed. The structure is incomplete, causing memory leaks.

        This behavior is to ensure proper release of resources. Since we only know the type of the parent class, the compiler cannot determine which subclass object the pointer points to, so it can only call the destructor of the parent class to release resources.

No virtual destruction:

#include<iostream>
#include<vector>
using namespace std;
class A {
public:
	virtual void prints() {
		cout << "A::prints" << endl;
	}
	A() {
		cout << "A:构造函数" << endl;
	}
	virtual ~A() {
		cout << "A:析构函数 " << endl;
	}
};
class B:public A {
public:
	virtual void prints() {
		cout << "B::prints" << endl;
	}
	B() {
		cout << "B:构造函数" << endl;
	}
	~B() {
		cout << "B:析构函数 " << endl;
	}
};
int main() {
	A *b = new B();
	b->prints();
	delete b;
	b = NULL;
	return 0;
}

Virtual destruction:

#include<iostream>
#include<vector>
using namespace std;
class A {
public:
	virtual void prints() {
		cout << "A::prints" << endl;
	}
	A() {
		cout << "A:构造函数" << endl;
	}
	virtual ~A() {
		cout << "A:析构函数 " << endl;
	}
};
class B:public A {
public:
	virtual void prints() {
		cout << "B::prints" << endl;
	}
	B() {
		cout << "B:构造函数" << endl;
	}
	~B() {
		cout << "B:析构函数 " << endl;
	}
};
int main() {
	A *b = new B();
	b->prints();
	delete b;
	b = NULL;
	return 0;
}

 

Analysis: You can see that the destructor is first destructed from the subclass, and then destructed from the parent class 

Guess you like

Origin blog.csdn.net/Ricardo_XIAOHAO/article/details/132741040