Three characteristics of polymorphism


Polymorphism is when class objects with different inheritance relationships call the same function, resulting in behaviors with different effects.

static polymorphism

Calling the same function produces behaviors with different effects. Isn’t this just function overloading! Function overloading is actually a kind of static polymorphism. Different functions are called with the same function name and different parameters, but which function to call is determined at the compilation stage. Function overloading is a type of compile-time binding, also known as static binding. Commonly used stream insertion and fluid fetching are also a kind of function overloading

dynamic polymorphism

Dynamic polymorphism is the main content of this article. When calling a function, it has nothing to do with the type but with the object it stores (ordinary calls are by type). Which function to call is only known at runtime, which is also called runtime binding, that is, dynamic binding.

The composition of polymorphism must meet two conditions:

1. It must be called by reference or pointer of the parent class as a formal parameter

Why must it be a reference or pointer of the parent class? For this question, "Deep Exploration of the C++ Model" said: "The reason why a pointer or a reference supports polymorphism is that they do not cause any "type-related memory delegation operations; they will be subject to change . " It's just the size and interpretation of the memory they point to ".

The above words can be understood as follows:

  1. The pointer and reference types only require the base address and the memory size of the object pointed to by the pointer, which has nothing to do with the type of the object, which is equivalent to interpreting the pointed memory as a pointer or reference type.
  2. If the subclass object is directly assigned to the parent class object, the memory model will be involved, and the compiler will avoid the virtual mechanism and cannot achieve the effect of polymorphism.

2. The called function must be a virtual function

virtual function

The so-called virtual function is the function modified by the virtual keyword

class Person 
{
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }//这就是虚函数
};

Although both virtual functions and virtual inheritance use virtualkeywords, there is no connection between the two

Rewriting (covering) of virtual functions

If the subclass has the same virtual function as the parent class ( the return value type, function name, and parameter list are the same), then the virtual function of the subclass is said to override the virtual function of the parent class.

Here is one thing to note: if the parent class adds it when declaring it virtual, even if the subclass does not add it when declaring the function with the same name, it virtualwill still be rewritten (it can be understood that the subclass inherits the virtual attribute when inheriting the parent class), but this is not standardized, and it is recommended not to write this way.

The rewriting of virtual functions can also be called the coverage of virtual functions, because a class with a virtual function has a virtual function table, and the subclass will inherit the virtual function table of the parent class when inheriting.

Using virtual function rewriting to achieve polymorphism

class Person
{
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person 
{
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
	
};
void Func(Person& p)
{
	p.BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st);
	return 0;
}

insert image description here

It can be seen that although I always call the Func function, in the end the Func function helped me call different functions, which is polymorphism.

After having polymorphism, when calling a function, first check whether the function constitutes polymorphism. If it constitutes polymorphism, then you don’t need to consider the type. You only need to see what kind of object is stored in the variable, and call the function according to the object;

Two exceptions for rewriting

1. Covariation

The return type of the rewriting of the parent class function by the subclass can be different, but it must return the pointer or reference of the parent-child class relationship (that is, the virtual function of the base class returns the pointer or reference of the base class object, and the virtual function of the derived class returns the pointer or reference of the derived class object), which is called covariance

class Person 
{
public:
	virtual A* f() {return new A;}
};
class Student : public Person 
{
public:
	virtual B* f() {return new B;}
};
2. The function name of the destructor is different

The function name of the destructor must be the same as the class name, that is to say, the name of the destructor of each class is different, but in fact, the compiler will uniformly process the function name of the destructor as destructor.

The destructor can not only be rewritten, but it is recommended to define the destructor as a virtual function .

If I define an object of a subclass and assign the subclass object to a pointer of the parent class, when I release the parent class, only the destructor of the parent class will be called, that is to say, only the resources of the parent class in the subclass are released, but the resources of the subclass are not released, which may cause a memory leak.

If I define the destructor as a virtual function and rewrite it, then when I release the pointer of the parent class, I call the destructor of the subclass. The destructor of the subclass cleans up the resources of the parent class through the destructor of the parent class, and also cleans up its own resources.

class Person
{
public:
	//virtual ~Person
	~Person()
	{
		
		cout << "~Person" << endl;
	}

public:
	int _a;
};

class Student :public Person
{
public:
    //virtual ~Student
	~Student()
	{
		cout << "~Student" << endl;
	}

public:
	int _b;
};

int main()
{
	Person *p1=new Person;
	Person* p2 = new Student;

	delete p1;
	delete p2;

	return 0;
}

insert image description here

override and final in C++11

1. Functions modified by the final keyword cannot be rewritten

class Car
{
public:
	virtual void Drive() final {}
};
class Benz :public Car
{
public:
	virtual void Drive() {cout << "Benz-舒适" << endl;}
};  

insert image description here

2. Use the override keyword to check whether the function is overridden, and report an error if it is not overridden

class Car
{
public:
	virtual void Drive()  {}
};
class Benz :public Car
{
public:
	virtual void Drive() override { cout << "Benz-舒适" << endl; }
};

overload, override (override), redefine (hide)

overload

1. To be in the same scope

2. The function name is the same, the parameter list is the same, and the return value can be different

rewrite (overwrite)

1. The two functions are in the scope of the parent class and the subclass respectively

2. The return value is the same (except for covariance), the function name is the same, and the parameter list is the same

3. Only virtual functions constitute rewriting

redefine (hidden)

1. The two functions are in the scope of the parent class and the subclass respectively

2. If the function name is the same, as long as it does not constitute rewriting, it is redefinition

abstract class

There is also a pure virtual function corresponding to the virtual function. As long as it is added at the end of the virtual function declaration, =0the virtual function becomes a pure virtual function. A class is an abstract class if it contains pure virtual functions. Abstract classes cannot instantiate objects, and if subclasses that inherit abstract classes do not rewrite pure virtual functions, subclasses are also an abstract class that cannot instantiate objects. Pure virtual functions specify that subclasses must be rewritten. In addition, pure virtual functions reflect interface inheritance.

class A
{
public:
	virtual void test() = 0;
	int _a;
};

class B :public A
{
public:
	virtual void test()
	{
		cout << "重写了纯虚函数" << endl;
	}
	int _b;
};
int main()
{
	A a;
	B b;

	return 0;
}

insert image description here

Interface Inheritance and Implementation Inheritance

The inheritance of ordinary functions is a kind of implementation inheritance. The subclass inherits the functions of the parent class, can use the function, and inherits the implementation of the function. The inheritance of virtual functions is a kind of interface inheritance. The subclass inherits the interface of the virtual function of the base class. The purpose is to rewrite, achieve polymorphism, and inherit the interface. So if you don't implement polymorphism, don't define functions as virtual functions.

The principle of polymorphic implementation

First, let's calculate the size of the following class

class A
{
public:
    virtual void test();
}

According to our class and object phase, a class without member variables is an empty class, and the size of an empty class is 1 byte, which is used to occupy space.

But the size of this class is 4 bytes

insert image description here

This is because if there is a virtual function in a class, there will be a hidden pointer in the class, which points to a virtual function table.

virtual function table

insert image description here

It can be seen that the address of the virtual function is stored in the virtual function table. The so-called rewriting of the virtual function is actually to overwrite the address of the rewritten virtual function on the address of the original virtual function.

insert image description here

Because each class has its own independent virtual function table, different class objects can access different virtual functions through different virtual function tables.

The virtual function tables are all terminated with nulls under vs, but not under Linux;

If a variable stores an object of a subclass, then the first four bytes in the variable are the virtual function table corresponding to the subclass, and the virtual function corresponding to the subclass is stored in the table. If the object of the parent class is placed, then the first four bytes of the variable are the virtual function table of the parent class, which stores the address of the virtual function corresponding to the parent class. The generation of polymorphism is to find the virtual function table according to the first four bytes of the object, and call the virtual function in it.

Storage location of the virtual function table

In addition, the virtual function table is stored in the constant area (code segment). The address of each area can be seen through the following code. The address of the virtual table is closest to which one can indicate which area the virtual table is in. After observation, it is indeed in the code segment.

class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}

	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}

	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}

private:
	int _b = 1;
	char _ch;
};

class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}

	void Func3()
	{
		cout << "Derive::Func3()" << endl;
	}
private:
	int _d = 2;
};
int main()
{
	int a = 0;
	cout << "栈:" << &a << endl;

	int* p1 = new int;
	cout << "堆:" << p1 << endl;

	const char* str = "hello world";
	cout << "代码段/常量区:" << (void*)str << endl;

	static int b = 0;
	cout << "静态区/数据段:" << &b << endl;
	Base be;
	cout << "虚表:" << (void*)*((int*)&be) << endl;

	Base* ptr1 = &be;
	int* ptr2 = (int*)ptr1;
	printf("虚表:%p\n", *ptr2);

	Derive de;
	cout << "虚表:" << (void*)*((int*)&de) << endl;

	Base b1;
	Base b2;

	return 0;
}

Replenish

The subclass not only has the members inherited from the parent class, but also has its own unique members. That is to say, the subclass not only has the virtual functions rewriting the parent class, but also has its own unique virtual functions.

For single-inheritance classes: subclass-specific virtual functions and rewritten virtual functions are placed in the same virtual function table

For a subclass with multiple inheritance: as many classes with virtual functions as it inherits, there are several virtual function tables, but the virtual functions unique to the subclass will be placed in the first virtual function table

Some questions about polymorphism

  1. what is polymorphism

Answer: Polymorphism is a variety of forms. The behavior of different objects doing the same thing and producing different effects can be called polymorphism.

  1. Can an inline function be a virtual function?

Answer: Yes, but the compiler ignores the inline attribute, and this function is no longer inline, because the virtual function must be placed in the virtual table.

  1. Can a static member be a virtual function?

​ Answer: No, because the static member function does not have a this pointer, and the virtual function table cannot be accessed by calling the type:: member function, so the static member function cannot be placed in the virtual function table.

  1. Can a constructor be virtual?

Answer: No, because the virtual function table pointer in the object is initialized at the constructor initialization list stage.

  1. Is object access to ordinary functions faster or virtual functions faster?

Answer: First of all, if it is an ordinary object, it is as fast. If it is a pointer object or a reference object, the ordinary function called is fast, because it constitutes polymorphism, and calling a virtual function at runtime needs to look up in the virtual function table.

  1. At what stage is the virtual function table generated and where does it exist?

Answer: The virtual function table is generated during the compilation stage, and generally exists in the code segment (constant area).

Guess you like

Origin blog.csdn.net/m0_62633482/article/details/130868572