C++: polymorphism explained

1. The concept of polymorphism

The concept of polymorphism:The same behavior will be generated when completed by different objects Different states.

Example: Take buying tickets as an example, soldiers, students, ordinary people( Subcategories) are allpeople(parent category< a i=13>), but military personnel can buy tickets with preferred votes, students can buy tickets at half price, and ordinary people can buy tickets at full price. Thereforein order to achieve polymorphism one must first inherit.



2. Definition and implementation of polymorphism

2.1 Conditions for polymorphism

Polymorphism is when class objects with different inheritance relationships call the same function, resulting in different behaviors. For example, the above example defines the parent class Person and lets the subclass Student inherit Preson. Person objects buy tickets at full price, and Student objects buy tickets at half price.

An inheritance relationship has been formed. To achieve polymorphism, the following two conditions are required: :

  1. Virtual functions must be called throughpointers or references of base classes
  2. The called function must be a virtual function, andThe derived class must override the virtual function of the base class

Please remember these two conditions first, and you will understand after explaining the principles of virtual functions and polymorphism later.


2.2 Virtual functions

Virtual function: The class member function modified byvirtual is called a virtual function.

class Person {
    
    
public:
	//这里和菱形虚拟继承公用了关键字,但两者是没有关联的
	virtual void BuyTicket() {
    
     cout << "买票-全价" << endl;}
};

2.3 Rewriting (overwriting) of virtual functions

Rewriting (overwriting) of virtual functions: There is a virtual function in the derived class that is exactly the same as the base class (that is, the return value of the derived class virtual function and the base class virtual function The type, function name, and parameter list are exactly the same), it is said that the virtual function of the subclass overrides the virtual function of the base class.
Rewriting is easier to understand, and the coverage is somewhat theoretical.

class Person {
    
    
public:
	virtual void BuyTicket() {
    
     cout << "买票-全价" << endl; }
};
class Student : public Person {
    
    
public:
	virtual void BuyTicket() {
    
     cout << "买票-半价" << endl; }
	/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因
	为继承后基类的虚函数被继承下来了所以认为派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议
	这样使用*/
	/*void BuyTicket() { cout << "买票-半价" << endl; }*/  //这个写法是可以的
};
void Func(Person& p)  
{
    
    
	p.BuyTicket();  //因为是父类引用去调用,所以构成多态
}
int main()
{
    
    
	Person ps;
	Student st;
	Func(ps);
	Func(st);
	return 0;
}

Let’s talk about implementation inheritance and interface inheritance:

  1. The inheritance of ordinary functions is a kind of implementation inheritance. The derived class inherits the base class function, can use the function, and inherits the implementation of the function.
  2. The inheritance of virtual functions is a kind of interface inheritance. The derived class inherits the interface of the base class virtual function. The purpose is to override and achieve polymorphism. What it inherits is the interface. . Thereforeif polymorphism is not implemented, do not define the function as a virtual function.

Exceptions to virtual function overriding:

  1. Covariance (the return value types of base class and derived class virtual functions are different)
    When a derived class overrides a base class virtual function, the return value type of the base class virtual function can be different. But it must be a pointer or reference to the parent-child relationship (it can also be other parent-child classes), which is called covariance.
class A{
    
    };
class B : public A {
    
    };
class Person {
    
    
public:
 virtual A* f() {
    
    return new A;}
};
class Student : public Person {
    
    
public:
 virtual B* f() {
    
    return new B;}
};
  1. Rewriting of destructors (the names of the base class and derived class destructors are different)
    The destructors seem to have different names, which violates the rules of virtual function rewriting. However, in order to ensure the correct release of resources, the compiler will perform special processing on the destructor. will be unified into destructor() after compilation.
class Person {
    
    
public:
	virtual ~Person() {
    
     cout << "~Person()" << endl; }
};
class Student : public Person {
    
    
public:
	virtual ~Student() {
    
     cout << "~Student()" << endl; }
};
// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函
//数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{
    
    
	Person* p1 = new Person;
	Person* p2 = new Student;  //如果父类析构不设计为虚函数的话可能造成内存泄漏
	delete p1;
	delete p2;
	return 0;
}

2.4 C++11 override and final

C++ has strict requirements for virtual function rewriting. Sometimes the rewriting cannot be done due to different letter order, etc., butthe compiler will not report an error in this case, which can help users detect whether to override. override and final, it would be too troublesome to correct it after discovering that the running results are wrong. Therefore, C++11 introduces two keywords:

  1. final: Modify the virtual function to indicate that the virtual function cannot be overridden.
// final用来修饰虚函数用处不大,设计虚函数本就是为了让子类重写实现多态
class Car
{
    
    
public:
	virtual void Drive() final {
    
    }
};
class Benz :public Car
{
    
    
public:
	virtual void Drive() {
    
     cout << "Benz-舒适" << endl; }   //这里会报错
};

//final还可以用来修饰类,被修饰的类不能被继承
class A final  
{
    
    
	//……
};

class B : public A  //这里会报错
{
    
    };
  1. override: Check whether the derived class virtual function overrides a virtual function of the base class. If it does not, a compilation error will be reported.
class Car {
    
    
public:
	virtual void Drive() {
    
    }
};
class Benz :public Car {
    
    
public:
	virtual void Drive() override {
    
     cout << "Benz-舒适" << endl; }
};

2.5 Comparison of overloading, rewriting (overwriting), and hiding (redefinition)

  • Overloading: (1) Two functions are in the same scope. (2) If the function names are the same, the parameter lists must be different.
  • Hidden: (1) The two functions are in the scope of the base class and the derived class respectively. (2) The function names are the same.
  • Rewrite: (1) The two functions are in the scope of the base class and the derived class respectively. (2) The function name, parameters, and return value must be the same (with exceptions) (3) Both functions must be virtual functions.

In the above relationship, the condition for hiding is simpler than overwriting, that is to sayThe functions of the same name in the two base classes and derived classes do not constitute overriding, but they constitute hiding .

//重载
//在同一作用域并且参数列表有区别,属于重载
void fun(int a)
{
    
    }

void fun(double a)
{
    
    }


//隐藏
//两个同名函数在基类和派生类作用域内,不构成重写就属于隐藏
class A
{
    
    
	void fun(int a)
	{
    
    }
};

class B : public A
{
    
    
	void fun(int a)
	{
    
    }
};

//重写
//两个同名函数在基类和派生类作用域内,并且都是虚函数
//函数名、参数列表、返回值都必须一致(除去例外),才能构成重写
class C
{
    
    
	virtual void fun(int a)
	{
    
    }
};

class D : public C
{
    
    
	virtual void fun(int a)
	{
    
    }
};



3.Abstract class

Write =0 after the virtual function, then this function is a pure virtual function. A class that contains pure virtual functions is called an abstract class (also called an interface class). Abstract classes cannot instantiate objects. . A derived class cannot instantiate an object after inheritance. Only by overriding a pure virtual function can a derived class instantiate an object. Pure virtual functions specify that derived classes must be rewritten. In addition, pure virtual functions also reflect interface inheritance.

//抽象类的应用场景:比如图形就可以设计为抽象类
//三角形、正方形等继承了该抽象类后重写虚函数,就形成一个具体的类,可以实例化

class Graphics
{
    
    
public:
	virtual double GetArea() = 0
	{
    
    }//抽象类里面可以设计图形共有的接口,比如求面积等
};

class Square : public Graphics  //正方形
{
    
    
public:
	virtual double GetArea()
	{
    
    
		return side_length * side_length;
	}
private:
	int side_length = 5;
};

class rotundity : public Graphics  //圆形
{
    
    
public:
	virtual double GetArea()
	{
    
    
		return 3.14 * radius * radius;
	}
private:
	int radius = 5;
};

int main()
{
    
    
	Square sq;
	rotundity ro;
	cout << "正方形面积:" << sq.GetArea() << endl;
	cout << "圆形面积:" << ro.GetArea() << endl;
}



4. The principle of polymorphism

//sizeof(Base)是多少? --答案是8(我是32位程序),除了一个int还有一个指针变量
class Base
{
    
    
public:
	virtual void Func1()
	{
    
    
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};

Insert image description here

Let’s take a look at what this pointer points to:
Insert image description here
I just said it directly, this pointer points to a table (pointer array),The table stores the function address of the virtual function,This table is called the virtual function table (referred to as virtual function table) table), The pointer in this object is called a virtual table pointer. As for why the function address is not stored directly in the object, the main reason is to save space (all objects can use a common virtual table). Why is it designed this way? Let's look down.

//编译器是在编译阶段就确定了调用是否满足多态
//(1)对于满足多态的调用,编译器通过找虚表指针,接着找到虚表中函数地址进行调用
//     多态调用也只是傻傻的执行指令而已
//(2)对于不构成多态的普通调用,编译器通过函数名和参数类型就可以确定调用那个函数

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 Mike;
	Func(Mike);
	Student Johnson;
	Func(Johnson);
	//这里不构成多态,对象调用而不是指针或引用,按正常调用规则即可
	Mike.BuyTicket();  
	return 0;
}

Here you can combine it with assembly code to understand:
Insert image description here
Virtual function rewriting is actually inheriting the parent class ,Use your own virtual table to overwrite the parent class's virtual table, so overwriting is also called overwriting.


5. Virtual function table of single inheritance and multiple inheritance relationships

PS: This part is not particularly important.

5.1 Single inheritance

With the previous analysis, there is only one thing to say about single inheritance:For those virtual functions that have not been overridden, they also exist in the virtual table, as to whether it is stored in the first position or last, it depends on the implementation of the compiler.


5.2 Multiple inheritance

class A
{
    
    
public:
	virtual void fun(int a)
	{
    
    }
};

class B
{
    
    
public:
	virtual void fun(int b)
	{
    
    }
};

class C : public A, public B  //继承A、B两个类
{
    
    
public:
	virtual void fun(int c)
	{
    
    }
};

int main()
{
    
    
	C c;
	A* p1 = new C;
	B* p2 = new C;
	p1->fun();
	p2->fun();
	delete p1, p1 = nullptr;
	delete p2, p2 = nullptr;
	return 0;
}

Insert image description here

In the above code, C inherits A and B, and rewrites fun(), covering two positions, so there are two virtual tables. This is also to consider the virtual functions with different names in A and B. Therefore it is not merged into a virtual table. But we found thatthe contents pointed to by these two virtual tables are different! ! !
This is actually a kind of encapsulation. In the code, p1 and p2 ultimately call the same function. P2 does one thing before calling the real function, that is Adjust the pointer variable pointing to.
Insert image description here


5.3 Diamond inheritance and polymorphism

I said beforeIn practice, diamond inheritance should be avoided. Diamond virtual inheritance plus polymorphism will make the object model extremely complicated. Here Without going into details, those who are interested can read the following article:

Guess you like

Origin blog.csdn.net/2301_76269963/article/details/133843272