[C++] Polymorphism (Part 1) -- abstract class, virtual function, virtual function table

Insert image description here

1. The concept of polymorphism

In layman's terms, polymorphism refers to multiple forms. The specific point is to complete a certain behavior. When different objects complete it, different states will be produced.
For example, take the act of buying a ticket:
when ordinary people buy tickets, they buy tickets at full price; when
students buy tickets, they buy tickets at half price; when
soldiers buy tickets, they buy tickets first.

This example is polymorphism, where different identities correspond to different fares.

2. Definition and implementation of polymorphism

2.1 Conditions for polymorphism

There are two important conditions for polymorphism in inheritance :

  1. Virtual functions must be called through a pointer or reference to the base class
  2. The derived class must rewrite the virtual function of the base class ( the three requirements for rewriting are the same: the same return value , the same function name , and the same parameter list )
    . We write a polymorphic code according to the conditions of polymorphism:
class Person
{
    
    
public:
	virtual void BuyTicket()
	{
    
    
		cout << "买票全价" << endl;
	}
};
class Student : public Person
{
    
    
	virtual void BuyTicket()
	{
    
    
		cout << "买票半价" << endl;
	}
};

void func(Person& p)
{
    
    
	p.BuyTicket();
    cout << endl;
}

Insert image description here
Insert image description here

2.2 Virtual functions

Virtual function: A class member function modified by virtual is called a virtual function.

virtual void BuyTicket()
{
    
    
	cout << "买票全价" << endl;
}

Insert image description here

2.3 Rewriting 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 type, function name, and parameter list of the derived class virtual function and the base class virtual function are exactly the same), which is called a subclass The virtual function overrides the virtual function of the base class.

class Person
{
    
    
public:
	virtual void BuyTicket()
	{
    
    
		cout << "买票全价" << endl;
	}
};
class Student : public Person
{
    
    
public:
	virtual void BuyTicket()
	{
    
    
		cout << "买票半价" << endl;
	}
};
class Teacher : public Person
{
    
    
public:
	// 子类可以重写虚函数时可以不加 virtual,但是不规范,不推荐这样写
	void BuyTicket()
	{
    
    
		cout << "买票半价" << endl;
	}
};

Note: When overriding the base class virtual function, the virtual function of the derived class can also constitute an override without adding the virtual keyword (because after inheritance, the virtual function of the base class is inherited and the derived class still maintains the virtual function. attribute), but this writing method is not very standardized and is not recommended to be used in this way.

2.4 Two exceptions to virtual function overriding

2.4.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 is different from the base class virtual function. That is, when a base class virtual function returns a pointer or reference to a base class object, and a derived class virtual function returns a pointer or reference to a derived class object, it is called covariance.

// 以自身父子类的指针/引用做返回值
class Person
{
    
    
public:
	virtual Person* BuyTicket()
	{
    
    
		cout << "virtual Person* BuyTicket()" << endl;
		return nullptr;
	}
};
class Student : public Person
{
    
    
public:
	virtual Student* BuyTicket()
	{
    
    
		cout << "virtual Person* BuyTicket()" << endl;
		return nullptr;
	}
};


// 以其他父子类指针/引用做返回值
class A
{
    
    };
class B : public A
{
    
    };

class Person
{
    
    
public:
	virtual A* BuyTicket()
	{
    
    
		cout << "virtual A* BuyTicket()" << endl;
		return nullptr;
	}
};
class Student : public Person
{
    
    
public:
	virtual B* BuyTicket()
	{
    
    
		cout << "virtual B* BuyTicket()" << endl;
		return nullptr;
	}
};

void func(Person* p)
{
    
    
	p->BuyTicket();
	cout << endl;
}
int main()
{
    
    
	Person p;
	func(&p);

	Student s;
	func(&s);

	return 0;
}

Insert image description here
Insert image description here

Here , whether it is using the pointer/reference of its own parent-child class as the return value, or using the pointer/reference of other parent-child class as the return value, both methods are possible. Still constitutes polymorphism.

2.4.2 Rewriting of destructor (base class and derived class analysis) Insert picture description here

The name of the constructor is different)
If the destructor of the base class is a virtual function, then as long as the destructor of the derived class is defined, regardless of whether the virtual keyword is added, it will constitute an override with the destructor of the base class, although the base class and The derived class destructor has a different name. Although the function names are different, it seems to violate the rewriting rules. In fact, it is not the case. It can be understood that the compiler has done special processing for the name of the destructor. After compilation, the name of the destructor is unified into destructor.
When the following situation occurs, our destructor must be written as a virtual function:

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

int main()
{
    
    
	Person* p = new Person;
	delete p;

	Person* s = new Student;
	delete s;

	return 0;
}

If we do not write the destructor as a virtual function, then when destructing the subclass object, it is not a polymorphic call, but only the sliced ​​part of the parent class is destructed. Such destruction does not change the subclass Complete release of the object's resources may cause memory leaks and other problems.

2.4.3 Multiple choice test

What does the following program output?

class A
{
    
    
public:
	virtual void func(int val = 1) 
	{
    
     
		std::cout << "A->" << val << std::endl; 
	} 
	virtual void test() 
	{
    
    
		func();
	}
};
 
class B : public A
{
    
    
public:
	void func(int val = 0) 
	{
    
     
		std::cout << "B->" << val << std::endl; 
	}
};
 
int main()
{
    
    
	B* p = new B;
	p->test();
	return 0;
}

A: A->0 B: B->1 C: A->1 D: B->0 E: Compilation error F: None of the above are correct

Let’s analyze this question:
1. First, when entering the main function, a new object of subclass B is first created. Subclass B inherits parent class A in a public way, so the public methods in parent class A are also in subclass B. Public methods exist, and all B objects can call the public methods in classes A and B. Therefore, the B pointer object p can call the test() method normally, which reflects the inherited attributes;
2. When entering the test() method, the implicit type of this pointer is the A-type pointer ( parent class pointer ), test() The func() method is called again in the method. The func() function in the parent class A is a virtual function. Although the func() function in the subclass B does not add virtual, func() is still a virtual function. At the same time, the func() function of the parent and child classes is virtual. The return value is the same, the function name is the same, and the parameters are the same (the type, order, and number of parameters are the same, it does not matter whether the parameter names are the same or whether there are default values), so the func() functions of A and B constitute a virtual Function rewriting ;
3. At this time, the parent class pointer is calling the virtual function func(), but the object pointed to by the pointer this is the subclass object, so the this pointer calls the func() method of the subclass . It should be noted here that the virtual function rewrites the function body. The header of the function directly takes the header of the parent class virtual function and uses it. Although the header is changed when the subclass is rewritten, the parent class is still used when the compiler calls it. The head of the func() function is void func(int val = 1). Therefore, the result is B:B->1.
Insert image description here

Summary:
1. When making a polymorphic call, the pointer points to the parent class object to call the parent class method, and points to the subclass object to call the subclass method;
2. When making the polymorphic call, the virtual function rewrites the function body, and the header is directly used. The header of the parent class virtual function;
3. When calling non-polymorphically, even if the virtual function is rewritten, its own header is used, that is, its own default value is used by default.

2.5 C++11 final and override

2.5.1 final

final: Modify the virtual function to indicate that the virtual function can no longer be overridden.

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

Insert image description here

2.5.2 override

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:
	void Drive() {
    
    }
};
class Benz :public Car {
    
    
public:
	virtual void Drive() override {
    
     cout << "Benz-舒适" << endl; }
};

Insert image description here

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

Insert image description here

3. Abstract class

3.1 What is an abstract class

If you write =0 after the virtual function, the function is a pure virtual function. A class containing 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 Car
{
    
    
public:
	virtual void Drive() = 0;
};
int main()
{
    
    
	Car c;
	return 0;
}

Insert image description here

Abstract classes here cannot instantiate objects. Subclasses must override pure virtual functions after inheritance, otherwise they cannot instantiate objects.

class Car
{
    
    
public:
	virtual void Drive() = 0;
};
class Benz : public Car
{
    
    
public:
	//virtual void Drive()
	//{
    
    
	//	cout << "Benz-舒适" << endl;
	//}
};
int main()
{
    
    
	Benz b;
	return 0;
}

Insert image description here

class Car
{
    
    
public:
	virtual void Drive() = 0;
};
class Benz : public Car
{
    
    
public:
	virtual void Drive()
	{
    
    
		cout << "Benz-舒适" << endl;
	}
};
int main()
{
    
    
	Benz b;
    b.Drive();
	return 0;
}

Insert image description here

3.2 Interface inheritance and interface implementation

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. 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. So if you don't implement polymorphism, don't define the function as a virtual function.

4. Virtual function table

Here is a common written test question: What is sizeof(Base)?

// 32位下的sizeof(Base)是多大呢?
class Base
{
    
    
public:
    virtual void Func1()
    {
    
    
    cout << "Func1()" << endl;
    }
private:
    int _b = 1;
};
int main()
{
    
    
    Base b;
    cout << sizeof(b) << endl;

    return 0;;
}

Insert image description here

Through observation and testing, we found that the b object is 8 bytes. In addition to the _b member, there is also an additional __vfptr placed in front of the object (note that some platforms may place it at the end of the object, which is related to the platform). This pointer in the object we It's called a virtual function table pointer (v stands for virtual, f stands for function) . A class containing a virtual function has at least one virtual function table pointer, because the address of the virtual function is placed in the virtual function table, and the virtual function table is also referred to as the virtual table. So what is put in this table in the derived class? Let’s analyze it further
Insert image description here

We make the following modifications to the above code. We add a derived class Derive to inherit Base, rewrite Func1() in Derive, and then add a virtual function Func2 and a normal function Func3 to Base.

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;
};
class Derive : public Base
{
    
    
public:
	virtual void Func1()
	{
    
    
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};
int main()
{
    
    
	Base b;
	Derive d;
	return 0;
}

Insert image description here

Through observation and testing, we discovered the following issues:

  1. There is also a virtual table pointer in the derived class object d. The d object is composed of two parts. One part is the member inherited from the parent class. The other part of the virtual table pointer, which is the existing part, is its own member.
  2. The virtual tables of base class b objects and derived class d objects are different. Here we find that Func1 has been rewritten, so the rewritten Derive::Func1 is stored in the virtual table of d, so the rewriting of virtual functions is also called For coverage , coverage refers to the coverage of virtual functions in the virtual table. Rewriting is called syntax, and overwriting is called principle layer.
  3. In addition, Func2 is a virtual function after being inherited, so it is put into the virtual table. Func3 is also inherited, but it is not a virtual function, so it is not put into the virtual table.
  4. The virtual function table is essentially a pointer array that stores virtual function pointers. Generally, a nullptr is placed at the end of this array.
  5. To summarize the virtual table generation of derived classes:
    a. First copy the contents of the virtual table in the base class to the virtual table of the derived class.
    b. If the derived class overrides a virtual function in the base class, use the derived class’s own Virtual functions cover the virtual functions of the base class in the virtual table
    c. The newly added virtual functions of the derived class are added to the end of the derived class's virtual table in the order in which they are declared in the derived class.
  6. Here is another question that is easy for children to confuse: where do virtual functions exist? Where does the virtual table exist? Answer: Virtual functions exist in virtual tables, and virtual tables exist in objects. Note that the answer above is wrong . But many children's shoes take this seriously. Note that the virtual table stores virtual function pointers, not virtual functions . Virtual functions are the same as ordinary functions and are stored in the code segment, but their pointers are stored in the virtual table. In addition, what is stored in the object is not a virtual table, but a virtual table pointer. So where does the virtual table exist? In fact, if we verify it, we will find that there is a code segment under vs.

Guess you like

Origin blog.csdn.net/Ljy_cx_21_4_3/article/details/135049059