Polymorphism and virtual function [C + +]

Posted by mobilekid on Thu, 06 Jan 2022 01:50:33 +0100

Polymorphism and virtual function [C + +]

1, Concept of polymorphism

1. Bind

Associating a function body with a function call is called binding. The process of associating programs with each other, that is, combining an identifier name with

A process of associating storage addresses. According to different binding stages, there are two different binding methods: static

Binding and dynamic binding.

2. Static binding (static binding)

Binding occurs in the compilation stage, and the function to be called is limited by object name or class name.

In C + +, the default function call binding method is early binding, which is also called static binding, that is, it is bound by the compiler before the program runs

And connector implementation.

3. Dynamic binding (dynamic binding)

Binding is executed when the program is running, and the function to be called is determined when the program is running.

Late binding delays binding until the program runs. When the program runs, it knows the type of object that actually receives the message

Type information binding function call, also known as dynamic binding or runtime binding.

4. Example description

In the {payroll() function, implement early binding for the call to salary().  

class employee {
	public:
		void salary() {};
};
class manager : public employee {
	public:
		void salary() {};	// Calculation and payment of manager's salary
};
class programmer : public employee {
	public:
		void salary() {};	// Calculation and payment of programmer's salary
};
class parttime : public employee {
	public:
		void salary() {};	// Calculation and payment of part-time salary
};
void payroll(employee& re) {	// payroll function
	re.salary();
}

Late binding delays binding until the program runs. When the program runs, it knows the type of object that actually receives the message

Type information binding function call, also known as dynamic binding or runtime binding. As follows:

// If late binding is used, run the following code before re Salary () is not associated with any function body

manager Harry;
programmer Ron;

// Really execute re Dynamic function binding during salary()

// Re points to Harry and re Salary() dynamic binding manager::salary()
payroll(Harry);

// Re points to Ron and re Salary() dynamically bind programmer::salary
payroll(Ron);

2, Virtual function

  • In C + + language, the polymorphism of runtime is realized by defining a function as a virtual function. Virtual functions are the basis of dynamic binding.
  • Is a non static member function.
  • In the class declaration, write virtual before the function prototype. Virtual is only used to describe the prototype in the class declaration and cannot be used in function implementation.
  • With inheritance, virtual functions are declared in the base class, and the same prototype functions are automatically virtual functions regardless of whether they are described in the derived class.
  • Essence: not overload declarations, but overrides.
  • Call method: through the base class pointer or reference, the execution will determine which function to call according to the class of the object pointed to by the pointer.

[example 1] for the above example, define the virtual function and analyze the operation results.

#include <iostream>
using namespace std;
class employee {
public:
	virtual void salary() {};
};
class manager : public employee {
public:
	// Calculation and payment of manager's salary
	void salary() {
		cout << "Calculation and payment of manager's salary" << endl;
	};
};
class programmer : public employee {
public:
	// Calculation and payment of programmer's salary
	void salary() {
		cout << "Calculation and payment of programmer's salary" << endl;
	};
};
class parttime : public employee {
public:
	// Calculation and payment of part-time salary
	void salary() {
		cout << "Calculation and payment of part-time salary" << endl;
	};
};
void payroll(employee& re) {	// payroll function
	re.salary();
}
int main() {
	manager Tom;
	payroll(Tom);
	programmer Andy;
	payroll(Andy);
	parttime(Saly);
	payroll(Saly);
	return 0;
}

[example 2]

#include <iostream>
using namespace std;
class Person {
public:
	virtual void Print() {        // Virtual functions in base classes
		cout << "Person!" << endl;
	}
};
class Worker :public Person {
private:
	int worker;
public:
	void Print() {        // Redefine in derived class Worker
		cout << "Worker!" << endl;
	}
	void Print1() {
		cout << "Other information of Worker" << endl;
	}
};
class Teacher :public Person {
public:
	void Print() {        // Redefine in derived class Teacher
		cout << "Teacher!" << endl;
	}
	void Print2() {
		cout << "Other information of Teacher" << endl;
	}
private:
	int teacher;
};
class Driver :public Person {
private:
	int driver;
};
int main() {
	Person* p;
	Worker w;
	Teacher t;
	Driver d;
	p = &w;
	p->Print();
	p = &t;
	p->Print();
	p = &d;
	p->Print();
	t.Print();
	return 0;
}

3, Working mechanism of dynamic linkage

When the compiler encounters the virtual} keyword during execution, it will automatically install the mechanism required for dynamic binding.

First, create a virtual function table VTABLE for these classes (note that they are not objects) that contain the virtual function VTABLE. In these virtual function tables, the compiler places the addresses of specific virtual functions of the class in the order of function declaration.

At the same time, place a pointer called vpointer, vptr for short, in each class with virtual function, and this pointer points to this

VTABLE of class.

vptr} is generally placed at the beginning of the object, and vptr is initialized as the place of VTABLE of this class in the constructor of the object

Address. When compiling a C + + program, follow the steps below:

(1) Create virtual function tables for various types. If there are no virtual functions, they will not be created;

(2) The virtual functions are not connected temporarily, but the addresses of each virtual function are put into the virtual function table;

(3) Connect each static function directly.

[code understanding]

class shape {
public:
	virtual double area() const {
		return 0;
	}
	virtual void draw(){}
};
class rectangle :public shape {
public:
	double area() const {
		return height * width;
	}
	void draw(){}
protected:
	double height, width;
};
class square :public shape {
public:
	void draw(){}
};
class circle :public shape {
public:
	double area() const {
		return PI * radius * radius;
	}
	void draw(){}
private:
	double radius;
};
shape *sa[]={new circle,new rectangle,new rectangle,new square}

[code analysis]

When a virtual function is called through a base class pointer, such as sa[0] above, it points to the starting address of the {circle object.

The compiler takes {VPTR from the object pointed to by} sa[0], and finds the corresponding virtual function table VTABLE according to} VPTR. Re root

Find the appropriate function according to the offset of the function in VTABLE.

Therefore, instead of calling "shape::area()" according to the pointer type, @ shape *, call "VPTR + offset"

Function at. Because getting the VPTR and determining the actual function address occurs at runtime, late binding is implemented.

If the derived class redefines the virtual function in the base class, the new version of the virtual function is saved in the VTABLE of the derived class

Address. If it is not redefined, the address of the base class virtual function is still used. The same virtual function is in VTABLE of derived class and base class

In the same position. If a new virtual function is added to the derived class, the compiler first accurately maps the virtual function of the base class to

Add the newly added virtual function address to VTABLE of the derived class. Virtual functions that exist only in derived classes cannot pass through bases

Class pointer call.

[example understanding]

#include <iostream>
using namespace std;
class B0 {
public:
	virtual void display() {
		cout << "B0::dispaly()" << endl;
	}
};
class B1 :public B0 {
public:
	void display() {
		cout << "B1::display()" << endl;
	}
};
class D1 : public B1 {
public:
	void display() { 
		cout << "D1::display()" << endl;
	}
};
void fun(B0* ptr) {
	ptr->display();
}
int main() {
	B0 b0, * p;		//Declare base class objects and pointers
	B1 b1;			//Declare derived class objects
	D1 d1;			//Declare derived class objects
	p = &b0;
	fun(p);			//Call base class B0 function member
	p = &b1;
	fun(p);			//Call derived class B1 function member
	p = &d1;
	fun(p);			//Call derived class D1 function member
	return 0;
}

Note:

1. About virtual functions:

(1) When the member function is defined as a virtual function in the base class, in order to achieve the effect of dynamic binding, the corresponding member function of the derived class and the base class

The number must not only have the same name, but also the return type, number of parameters and type. Otherwise, runtime polymorphism cannot be implemented.

(2) The "virtual" keyword in front of the virtual function in the base class cannot be omitted, and the "virtual" keyword of the virtual function in the derived class can be omitted. It remains after default

Is a virtual function.

(3) Inline functions cannot make virtual functions. Even if defined inside a class, it is still considered non inline at compile time.

(4) A virtual function must be a member function of a class, not a friend function or a static member function.

(5) Constructors cannot be defined as virtual functions, but destructors can be defined as virtual functions.

2. Comparison between virtual function and overloaded function

(1) Overloaded functions require the same function name and different parameter sequences; Virtual functions require these three terms (functions)

Name, return value type, and parameter sequence) are identical.

(2) Overloaded functions can be member functions or friend functions, while virtual functions can only be member functions.

(3) The call of overloaded function is based on the difference of the passed parameter sequence; Virtual functions are based on objects

To call virtual functions of different classes.

(4) Virtual functions show polymorphic functions at runtime, which is the essence of C + +; Overloaded functions show polymorphism at compile time.

[example]

#include <iostream>
#include <cstring>
using namespace std;
class A {
public:
	virtual int f() const {
		cout << "Base::f()" << endl;
		return 1;
	}
	virtual void f(string) const {}
	virtual void g() const {}
};
class B1 :public A {
public:
	void g() const{}
};
class B2 :public A {
public:
	int f() const {
		cout << "D2::f()" << endl;
		return 2;
	}
};
/*
class B3 :public A {
public:
	void f()const {		// Error, the return type of the virtual function cannot be modified
		cout << "D3::f()" << endl;
	}
};
*/
class B4 :public A {
public:
	int f(int)const {
		cout << "D4::f()" << endl;
		return 4;
	}
};
int main() {
	string s = "Hello";
	B1 d1;
	int x = d1.f();
	d1.f(s);
	B2 d2;
	x = d2.f();
	// d2.f(s); 		//  Error, f(string) is hidden
	B4 d4;
	x = d4.f(1);
	// x = d4.f(); 	//  Error, f () is hidden
	// d4.f(s); 		//  Error, f(string) is hidden
	A& br = d4;		// Up type conversion
	// br.f(1);		
	// Error, the base class interface does not have f(int), and the base class pointer cannot call a member of a derived class
	br.f();
	br.f(s);
	return 0;
}

IV. virtual destructor

In C + +, you cannot declare a virtual constructor because when the constructor is executed, the object has not been fully constructed and cannot be declared

Called in virtual function mode. However, you can declare a virtual destructor if you point to a derived class pair generated by {new} with a base class pointer

For example, when deleting a derived class object through the delete action on the base class pointer, there are two cases:

(1) If the base class destructor is not a virtual destructor, only the destructor of the base class will be called, and the destructor of the derived class will not be called

Is called, so the derived part of the memory space in the derived class object cannot be destructed and released.

(2) If the base class destructor is a virtual destructor, all destructors in the base class and derived classes will be called when the base class pointer is released

Function, all memory space in the derived class object will be released, including the part inheriting the base class. So the destructor in C + +

Usually a virtual destructor.

[example] usage example of virtual destructor.

#include <iostream>
using namespace std;
class B1 {
public:
	~B1() {
		cout << "use B1 Destructor for" << endl;
	}
};
class D1 :public B1 {
public:
	~D1() {
		cout << "use D1 Destructor for" << endl;
	}
};
class B2 {
public:
	virtual ~B2() {
		cout << "use B2 Destructor for" << endl;
	}
};
class D2 :public B2 {
public:
	~D2() {
		cout << "use D2 Destructor for" << endl;
	}
};
int main() {
	B1* b1 = new D1;
	delete b1;
	B2* b2 = new D2;
	delete b2;
	return 0;
}

Steps to achieve polymorphism:

(1) Declare the member function that needs to be called polymorphically as virtual in the base class;

(2) Override the virtual function of the base class in the derived class to realize their required functions;

(3) The pointer or reference of the base class points to the derived class object, and the virtual function is called through the pointer or reference.

In this way, the virtual function version in the derived class is called according to the pointer of the base class or the object actually pointed to by the reference (derived class).

If the derived class does not override the virtual function of the base class, the virtual function in the base class is called.

It should be noted that only through the pointer or reference of the base class can polymorphic calls of virtual functions be realized, and virtual functions can be called through objects

Number does not cause polymorphic calls.

C + + in order to reduce the overhead of program runtime and improve efficiency, the information that can be determined at compile time will not be delayed to runtime processing.

The binding of virtual functions is determined according to the object type of the implementation call. If the virtual function is called with a pointer or reference, it needs to be shipped to

The actual object type they point to is only known when the rows are bound, so late binding is performed. If you call a virtual function directly using an object, the object's

The type is known at compile time and will not be bound late.

[example]

#include <iostream>
using namespace std;
class Base {
public:
	virtual void vfunc() {
		cout << "Base::vfunc()" << endl; 
	}
};
class Derived1 : public Base {
public:
	void vfunc() { 
		cout << "Derived1::vfunc()" << endl;
	}
};
class Derived2 : public Base {
public:
	void vfunc() { 
		cout << "Derived2::vfunc()" << endl;
	}
};
int main() {
	Base b;
	Derived1 d1;
	Derived2 d2;
	b.vfunc();		// It is known that b is an object of type Base and is bound to Base:: vffunc() at compile time
	d1.vfunc();		// As above, bind to derived1:: vffunc() at compile time
	d2.vfunc();		// As above, compile and bind to derived2:: vffunc()
	b = d1;
	b.vfunc();		// Object call, without polymorphism, is still bound to base:: vffunc() at compile time
	Base* pb = &b;
	pb->vfunc();	// When the runtime is bound to pb, it points to the vffunc () of the type Base to which b belongs
	pb = &d1;
	pb->vfunc();	// When the runtime is bound to pb, it points to vffunc () of type Derived1 to which d1 belongs
	pb = &d2;
	pb->vfunc();	// When the runtime is bound to pb, it points to vffunc () of type Derived2 to which d2 belongs
}

Note: even when a base class pointer (Reference) points to a derived class object, it can only call the member functions that appear in the base class interface, not

Call member functions added in derived classes, even virtual functions.

[implementation of dynamic binding]

#include <iostream>
using namespace std;
class B1 {
	// static int n;
	int m;
public:
	void f() {}
};
class B2 {
	int m;
public:
	virtual void f() {}
};
class D1 :public B1 {
	int n;
public:
	void f() {}
};
class D2 :public B2 {
	int n;
public:
	void f() {}
};
class B3 {
	int n;
public:
	virtual void g() {}
	virtual void h() {}
};
int main() {
	cout << "sizeof(B1) = " << sizeof(B1) << endl;
	cout << "sizeof(B2) = " << sizeof(B2) << endl;
	cout << "sizeof(D1) = " << sizeof(D1) << endl;
	cout << "sizeof(D2) = " << sizeof(D2) << endl;
	cout << "sizeof(B3) = " << sizeof(B3) << endl;
	return 0;
}

[object layout]

5, Pure virtual functions and abstract classes

In object-oriented design, it is often necessary to abstract the commonness of a group of specific classes to form a more general base class from bottom to top

The public interface for this set of classes.

In the process of upward abstraction, we find that the higher the base class, the higher the degree of abstraction, and sometimes it is even difficult to define them

Some operations of are described in detail. The purpose of these base classes is no longer to create instances, but to describe the pie in the class hierarchy

The common characteristics of the derived classes provide a common interface for these derived classes. Such a class is "abstract", and the opposite concept is "concrete"

Class.

For example, cars, trains and ships are specific classes. They all have examples and can "drive"; You can use "transportation"

It is difficult to describe the "common" of the "driving vehicle" and the "driving vehicle" in the end

So this operation is an abstract operation, and this vehicle class is an abstract class.

1. Pure virtual function

In C + +, pure virtual functions are used to define abstract operations in classes, and pure virtual functions are not implemented.

The general form of declaring a pure virtual function is:

class name{

Virtual type function name (parameter table) = 0; / / pure virtual function

        ......

}

be careful:

(1) Pure virtual functions have no function body.

(2) The last "= 0" does not mean that the return value of the function is 0. It only plays a formal role, that is, it is a pure virtual function.

(3) This is a declaration statement, and there should be a semicolon at the end.

(4) If a pure virtual function is declared in a class and is not defined in its derived class, the virtual function is in the derived class

Is still a pure virtual function.

2. Abstract class

If there is at least one pure virtual function in a class, the class is called abstract class.

Abstract classes include not only pure virtual functions, but also virtual functions. Pure virtual functions in abstract classes may be defined in abstract classes

It may also be inherited and redefined from its abstract base class.

An important feature of abstract classes is that abstract classes must be used as base classes to derive other classes, not to create object instances directly.

Abstract classes serve two main purposes:

(1) Support general general concepts. For example, graphics, vehicles, etc., these concepts have no examples, but only use their tools

Body derived class instance.

(2) Provides a common interface for a set of derived classes, and the implementation of the interface is provided by each derived class. If we know that airplanes are a kind of traffic

Tool, you know it can drive, but how to drive needs to be realized by the "aircraft" class.

[make different sales reports with virtual functions]

#include <iostream>
#include <cstring>
using namespace std;
class Datebase {
public:
	int number;
	char goods_name[20];
	float price;
	char sale;
	Datebase(int n, char *goods, float p, char s) {
		number = n;
		strcpy(goods_name,goods);
		price = p;
		sale = s;
	}
	float couter() {
		float t;
		t = price * number;
		return t;
	}
	virtual void reporter() = 0;
};
class reporter1 :public Datebase {
public:
	reporter1(int n, char *goods, float p, char s) :Datebase(n, goods, p, s) {}
	void reporter() {
		cout << number << "		" << goods_name << endl;
	}
};
class reporter2 :public reporter1 {
public:
	reporter2(int n, char* goods, float p, char s) :reporter1(n, goods, p, s) {}
	void reporter() {
		cout << number << "		" << goods_name << "	" << price << "		" << sale << endl;
	}
};
class reporter3 :public reporter2 {
public:
	reporter3(int n, char* goods, float p, char s) :reporter2(n, goods, p, s) {}
	void reporter() {
		cout << number << "		" << goods_name << "	" << couter() << endl;
	}
};
int main() {
	int k;
	reporter3 p1(200, "pen", 9.0, 's');
	reporter3 p2(20, "paper", 10.0, 's');
	reporter3 p3(180, "ink", 1.0, 's');
	cout << "Input the reporter number you want:" << endl;
	cout << "1.simple style" << endl;
	cout << "2.completely style" << endl;
	cout << "3.counting style" << endl;
	cin >> k;
	if (k == 1) {
		reporter1* p;
		cout << "number		goods_name" << endl;
		cout << "---------------------" << endl;
		p = &p1;
		p->reporter();
		p = &p2;
		p->reporter();
		p = &p3;
		p->reporter();
	}
	else if (k == 2) {
		cout << "number		goods_name		price		saleornot" << endl;
		cout << "------------------------------------------------" << endl;
		reporter2* p;
		p = &p1;
		p->reporter();
		p = &p2;
		p->reporter();
		p = &p3;
		p->reporter();
	}
	else if (k == 3) {
		reporter3* p;
		cout << "number		goods_name		total_incoming" << endl;
		cout << "-----------------------------------------" << endl;
		p = &p3;
		p->reporter();
		p = &p2;
		p->reporter();
		p = &p1;
		p->reporter();
	}
	return 0;
}

When using abstract classes, pay attention to the following aspects:

(1) If a class contains at least one pure virtual function, the class is an abstract class. Can only be used as a base class. If one smokes

All member functions in an image class are pure virtual functions. This class is called a pure abstract class.

(2) When inheriting an abstract class, all pure virtual functions must be implemented (overridden) in the derived class, otherwise the derived class is also considered

As an abstract class.

(3) You cannot create an instance of an abstract class, but you can create an instance of a concrete subclass derived from an abstract class or define an instance of an abstract class

Pointers or references that point to objects of specific derived classes.

Example:

int main() {
	Shape s;		//The compiler reported an error
	Rectangle r(4, 5);
	Shape* ps = &r;  //OK, you can declare a Shape class pointer to a specific derived class instance
	cout << ps->area();
	Shape& rs = r;	 //OK, you can declare the Shape class reference and reference the specific derived class object
	cout << rs.perimeter();
}

(4) Abstract classes cannot be used as function parameter types, function return value types, or display conversion types.

Example:

class shape	{		// abstract class
public:
		virtual void draw() = 0;	// Pure virtual function
};
shape  x;		// Error, abstract class cannot create object
shape* p;		// Correctly, you can declare pointers to abstract classes
shape  f();		// Error, abstract class cannot be used as return type
void g(shape);	// Correct, abstract classes cannot be used as parameter types

(5) Abstract classes can also contain ordinary member functions. Pure virtual functions can be called in ordinary member functions because pure virtual functions are pushed

Late implementation in a specific derived class. Due to the late binding of virtual functions, this call will be reflected as a specific operation when the derived object implements this call

A call to a specific operation.

Example:

#include <iostream>
using namespace std;
class abstract {	//Abstract base class
public:
	virtual void pf() = 0;	//Pure virtual function
	void f() {		//Ordinary member function
		cout << "abstract::f()" << endl;
		pf();		//The object type that calls f() determines which class's pf() implementation to call
	}
};
class concrete : public abstract {
public:
	//Implement pure virtual functions in the base class interface
	void pf() { 
		cout << "concrete::pf()" << endl; 
	}
};
int main() {
	concrete c;
	c.f();
	return 0;
}

6, Comprehensive application

[question]

Using virtual function mechanism to write a simplified menu driver. When running this program, it displays the following four lines on the screen

Information:

        1 — dispaly "Good!"

        2 — display "Better!"

        3 — display "Best!"

        0 — Exit

The program then reads in the number entered by the user. If 0 is entered, exit; if 1 is entered, Good! Will be displayed!, The input is 2

Better!, Enter 3 to display Best!. Finally, the four lines of information listed above are displayed again and wait for another reply from the user

One entry.

[reference code]

#include <iostream>
using namespace std;
class Show {
public:
	virtual void display() {}
};
class display1 :public Show {
public:
	void display() {
		cout << "Dood!" << endl;
	}
};
class display2 :public Show {
public:
	void display() {
		cout << "Better!" << endl;
	}
};
class display3 :public Show {
public:
	void display() {
		cout << "Best!" << endl;
	}
};
class display0 :public Show {
public:
	void display() {
		exit(0);
	}
};
int main() {
	int key;
	Show* p[4];
	display1 good;
	display2 better;
	display3 best;
	display0 exit;
	p[1] = &good;
	p[2] = &better;
	p[3] = &best;
	p[0] = &exit;
	for (;;) {
		cout << " 1 — dispaly "Good! " << endl;
		cout << " 2 — display "Better! " << endl;
		cout << " 3 — display "Best! " << endl;
		cout << " 0 — Exit " << endl;
		cin >> key;
		p[key]->display();
	}
	return 0;
}

Class hierarchy design:

Encapsulation, inheritance and polymorphism are powerful mechanisms of object-oriented programming. One of the main tasks of object-oriented design is to design

Count classes and organize these classes through various relationships. In order to take advantage of the advantages of polymorphism, it is flexible and common to write at a higher level of abstraction

In object-oriented programs, inheritance is usually used to establish some class hierarchies.

There are two general ways to establish class hierarchy:

(1) It is top-down. First, design the base class describing general characteristics and operations, and then inherit the base class to obtain various derived classes to solve specific problems;

(2) From bottom to top, first complete the design of the underlying classes, then abstract their commonalities as needed, get the common base classes that describe their common interfaces, and finally write programs for the base classes at a higher level of abstraction.

It is also common to use both methods in design.

Topics: C++