Polymorphism of three characteristics of C + + - in-depth understanding of virtual functions

catalogue

1. What is polymorphism

1.1 cases of parent class pointer pointing to child class pointer

2. Basic use of polymorphic virtual functions

3. Polymorphic virtual function table

3.1 virtual function table of a single class

3.2 using inherited virtual function table

3.3 virtual function table of multiple inheritance

4. Modification of virtual function

4.1 modification of virtual function - final

4.2 modification of virtual function -- override

5. Missing subclass destructor

6. Pure virtual functions and abstract classes

6.1 when to use pure virtual functions

6.2 usage of pure virtual functions

6.3 precautions for pure virtual functions

1. What is polymorphism

The essence of polymorphism:

Formally, a unified parent class pointer is used for general processing,

However, in actual execution, this pointer may point to subclass objects,

Formally, the method of the parent class is called, but in fact, the method of the child class with the same name will be called.

1.1 cases of parent class pointer pointing to child class pointer

#include <iostream>
using namespace std;

class Father {
public:
	void play() {
		cout << "reach KTV sing..." << endl;
	}
};

class Son :public Father {
public:
	void play() {
		cout << "Let's fight the king together!" << endl;
	}
};

void party(Father** men, int n) {
	for (int i = 0; i < n; i++) {
		men[i]->play();
	}
}
int main(void) {
	Father father;
	Son son1, son2;
	Father* men[] = { &father, &son1, &son2 };

	party(men, sizeof(men) / sizeof(men[0]));

	system("pause");
	return 0;
}

  Operation screenshot:

  Solution:

Through virtual functions, polymorphism is realized. Add virtual before the function with the same name as the subclass in the parent class

class Father {
public:
	virtual void play() {
		cout << "reach KTV sing..." << endl;
	}
};

  Operation screenshot:

  2. Basic use of polymorphic virtual functions

Definition of virtual function:

Use virtual before the return type of the function

Only add virtual in the declaration of member functions, not in the implementation of member functions

Inheritance of virtual functions:

  1. If a member function is declared as a virtual function, its subclass [derived class] and the inherited member function in the subclass of the subclass are also automatically virtual functions.
  2. If you rewrite this virtual function in a subclass, you can no longer write virtual, but it is still recommended to write virtual, which is more readable!

3. Polymorphic virtual function table

3.1 virtual function table of a single class

Object, the first stored is "virtual function table pointer", also known as "virtual table pointer".

Then store non static data members.

Object, saved in the class code!

Object, which only stores virtual function tables and data members

(static data members of the class are stored in the data area and stored separately from the object)

After adding a virtual function, the memory space of the object remains unchanged! Add entries to virtual function table only

Multiple objects share the same virtual function table!

Hand drawn memory distribution:

Code example:  

#include <iostream>
using namespace std;

class Father {
public:
	virtual void func1() { cout << "Father::func1" << endl; }
	virtual void func2() { cout << "Father::func2" << endl; }
	virtual void func3() { cout << "Father::func3" << endl; }
	void func4() { cout << "Non imaginary function: Father::func4" << endl; }
public:  //In order to facilitate testing, public should be used in particular
	int x = 100;
	int y = 200;
	static int z;
};

typedef void (*func_t)(void);
int Father::z = 1;
int main(void) {
	Father father;

	// In the memory of objects containing virtual functions, the "virtual function table" is stored first
	cout << "Object address:" << (int*)&father << endl;

	int* vptr = (int*)*(int*)&father;
	cout << "Virtual function table pointer vptr: " << vptr << endl;

	cout << "Call the first virtual function: ";
	((func_t) * (vptr + 0))();

	cout << "Call the second virtual function:";
	((func_t) * (vptr + 1))();

	cout << "Call the third virtual function: ";
	((func_t) * (vptr + 2))();


	cout << "Address of the first data member: " << endl;
	cout << &father.x << endl;
	cout << std::hex << (int)&father + 4 << endl;
	cout << "Value of the first data member:" << endl;
	cout << std::dec << father.x << endl;
	cout << *(int*)((int)&father + 4) << endl;

	cout << "Address of the second data member: " << endl;
	cout << &father.y << endl;
	cout << std::hex << (int)&father + 8 << endl;
	cout << "Value of the second data member:" << endl;
	cout << std::dec << father.y << endl;
	cout << *(int*)((int)&father + 8) << endl;

	cout << "sizeof(father)==" << sizeof(father) << endl;

	Father father2;
	cout << "father Virtual function table:";
	cout << *(int*)(*(int*)&father) << endl;
	cout << "father2 Virtual function table:";
	cout << *(int*)(*(int*)&father2) << endl;

	system("pause");
	return 0;
}

  Operation screenshot:

Alternatively, you can use VS's object memory distribution analysis:

Add: / d1 reportSingleClassLayoutFather to the command line configuration of the project

  Compilation result:

3.2 using inherited virtual function table

#include <iostream>
using namespace std;

class Father {
public:
	virtual void func1() { cout << "Father::func1" << endl; }
	virtual void func2() { cout << "Father::func2" << endl; }
	virtual void func3() { cout << "Father::func3" << endl; }
	void func4() { cout << "Non imaginary function: Father::func4" << endl; }
public:  //In order to facilitate testing, public should be used in particular
	int x = 100;
	int y = 200;
};

class Son : public Father {
public:
    //Subclass rewriting virtual functions can be written without virtual or virtual
	void func1() { cout << "Son::func1" << endl; }
	virtual void func5() { cout << "Son::func5" << endl; }
};

Memory distribution:

  Subclass virtual function table construction supplement

3.3 virtual function table of multiple inheritance

#include <iostream>

using namespace std;

class Father {
public:
	virtual void func1() { cout << "Father::func1" << endl; }
	virtual void func2() { cout << "Father::func2" << endl; }
	virtual void func3() { cout << "Father::func3" << endl; }
	void func4() { cout << "Non imaginary function: Father::func4" << endl; }
public:
	int x = 200;
	int y = 300;
	static int z;
};

class Mother {
public:
	virtual void handle1() { cout << "Mother::handle1" << endl; }
	virtual void handle2() { cout << "Mother::handle2" << endl; }
	virtual void handle3() { cout << "Mother::handle3" << endl; }
public: //For ease of testing, use public permissions
	int m = 400;
	int n = 500;
};

class Son : public Father, public Mother {
public:
	void func1() { cout << "Son::func1" << endl; }
	virtual void handle1() { cout << "Son::handle1" << endl; }
	virtual void func5() { cout << "Son::func5" << endl; }
};

  Memory distribution:

VS compilation analysis:

 

4. Modification of virtual function

4.1 modification of virtual function - final

1. Used to modify a class so that it cannot be inherited

Understanding: make this class end!

class XiaoMi {
public:
	XiaoMi(){}
};

//If the inheritance method is not written, it defaults to private
class XiaoMi2 final : public XiaoMi  {
	XiaoMi2(){}
};

class XiaoMi3 : public XiaoMi2 {  //XiaoMi2 cannot be used as a base class

};

class Phone8848 final{    //This class will not be inherited

}

  2. It is used to modify the virtual function of a class so that the virtual function cannot be overridden in a subclass, but can be used

Understanding: make this function end!

class XiaoMi {
public:
	virtual void func() final;
};

void XiaoMi::func() { //No need to write final
	cout << "XiaoMi::func" << endl; 
}

class XiaoMi2 : public XiaoMi  {
public:
	void func() {}; // Wrong! func functions cannot be overridden, but derived classes can use
};

4.2 modification of virtual function -- override

  override can only be used to decorate virtual functions.

effect:

     1. Prompt the reader of the program. This function is the function of overriding the parent class.

     2. Prevent programmers from writing the function name wrong when rewriting the function of the parent class.

#include <iostream>
using namespace std;

class XiaoMi {
public:
	virtual void func() { cout << "XiaoMi::func" << endl; };
};

class XiaoMi2 : public XiaoMi  {
public:
	void func() override {}
	//void func() override;   Tell the programmer that func is a virtual function that overrides the parent class
	//void func1() override {} error! Because the parent class has no func1 virtual function
};

Remind the programmer to prevent the function name from being written incorrectly when overriding the virtual function of the parent class

  override only needs to be used in the function declaration, not in the function implementation.

  5. Missing subclass destructor

When the parent class pointer points to a subclass, using delete will cause the subclass destructor not to be called automatically

#include <iostream>
#include <Windows.h>
#include <string.h>

using namespace std;

class Father {
public:
	Father(const char* addr = "China") {
		cout << "Yes Father Constructor for" << endl;
		int len = strlen(addr) + 1;
		this->addr = new char[len];
		strcpy_s(this->addr, len, addr);
	}

	 ~Father() {
		cout << "Yes Father Destructor for" << endl;
		if (addr) {
			delete addr;
			addr = NULL;
		}
	}
private:
	char* addr;
};

class Son :public Father {
public:
	Son(const char* game = "Eat chicken", const char* addr = "China")
		:Father(addr) {
		cout << "Yes Son Constructor for" << endl;
		int len = strlen(game) + 1;
		this->game = new char[len];
		strcpy_s(this->game, len, game);
	}
	~Son() {
		cout << "Yes Son Destructor for" << endl;
		if (game) {
			delete game;
			game = NULL;
		}
	}
private:
	char* game;
};

int main(void) {
	cout << "----- case 1 -----" << endl;
	Father* father = new Father();
	delete father;

	cout << "----- case 2 -----" << endl;
	Son* son = new Son();
	delete son;

	cout << "----- case 3 -----" << endl;
	father = new Son();
	delete father;

	system("pause");
	return 0;
}

  Operation screenshot:

Solution: define the parent class destructor as a virtual function

  When the destructor of the Father class is defined as a virtual function,

If you use the delete operation on the pointer of the Father class,

  Dynamic destruct is used for this pointer:

If this pointer points to a subclass object,

Then it will call the destructor of this subclass first, and then call the destructor of its own class

  Modify parent class code:

virtual ~Father() {
		cout << "Yes Father Destructor for" << endl;
		if (addr) {
			delete addr;
			addr = NULL;
		}
	}

  Execution results:

6. Pure virtual functions and abstract classes

6.1 when to use pure virtual functions

Some classes do not need to be instantiated (do not need to create its object) from the perspective of reality and project implementation,

Some member functions defined in this class are just to provide a formal interface and prepare subclasses for concrete implementation.

At this time, this method can be defined as "pure virtual function", and the class containing pure virtual function is called abstract class.

6.2 usage of pure virtual functions

Usage: pure virtual function, using virtual and = 0

  Code example:

#include <iostream>
#include <string>

using namespace std;

class Shape {
public:
	Shape(const string& color = "white") { this->color = color; }
	virtual float area() = 0; //There is no need to do specific implementation
	string getColor() { return color; }
private:
	string color;
};

class Circle : public Shape {
public:
	Circle(float radius = 0, const string& color = "White")
		:Shape(color), r(radius) {}
	float area();
private:
	float r; //radius
};


float Circle::area() {
	return 3.14 * r * r;
}



int main() {
	//Illegal to use abstract class to create object!
	//Shape s;  

	Circle c1(10);
	cout << c1.area() << endl;

	Shape* p = &c1;
	cout << p->area() << endl;

	system("pause");
	return 0;
}

6.3 precautions for pure virtual functions

After the parent class declares a pure virtual function,

So its subclasses,

  1. Either implement this pure virtual function (the most common)
  2. Either continue to declare this pure virtual function as a pure virtual function, and this subclass also becomes an abstract class
  3. Or do nothing with this pure virtual function, which is equivalent to the previous case (this method is not recommended)


 

Tags: C++ Back-end

Posted on Wed, 27 Oct 2021 06:08:09 -0400 by fuji