Polymorphism of three characteristics of C + + object-oriented program

Concept of polymorphism

Different types of objects respond differently to the same message, which is called polymorphism. Generally speaking, it is to complete a certain behavior. When different objects complete it, they will produce different results

For example, if you go to an Internet cafe to get online, if you are a vip, the price of your Internet access will be lower than that of ordinary users; If you are not a vip, then your online price is the original price. This is a polymorphism in life

Definition and use of polymorphism

For example, the following program is a manifestation of polymorphism

class Vip
{
public:
	virtual void online()
	{
		cout << "7 Discount" << endl;
	}
};

class Common : public Vip
{
public:
	virtual void online()
	{
		cout << "No discount at full price" << endl;
	}
};

void fun(Vip& v)
{
	v.online();
}

void test()
{
	Vip v;
	Common m;
	fun(v);
	fun(m);
}

Operation results:

The vip object is assigned to the parent object v by reference. V calls the online function. At this time, it calls the online function of the base class and outputs a 70% discount; The common object is assigned to the parent object v by reference. V calls the online function. At this time, it calls the online function of the child class, and the output price is not preferential; This reflects that different objects will have different results when they complete one thing, which is polymorphism. What are the necessary conditions for polymorphism?

The premise of realizing polymorphism (indispensable):

  1. Polymorphism is embodied in base classes and derived classes, so polymorphism must have inheritance
  2. The function must be virtual
  3. Virtual functions need to be overridden by subclasses
  4. Call the virtual function through the pointer or reference of the parent class to slice

The fourth point is to understand the code

//polymorphic
void fun(Vip& v)
{
	v.online();
}
void fun1(Vip* v)
{
	v->online();
}

//Non polymorphic
void fun2(Vip v)
{
	v.online();
}

virtual function

If the keyword virtual is added to the function name, the function is a virtual function.
Format: virtual return value function name (parameter)

class Vip
{
public:
	virtual void online()
	{
		cout << "7 Discount" << endl;
	}
};

Rewriting of virtual functions

Rewriting (covering) of virtual functions: a virtual function in a derived class that is exactly the same as the base class is called the virtual function of a subclass, which rewrites the virtual function of the base class

Rewrite (override) requirements: the function name, parameter list and return value of the virtual function in the derived class should be exactly the same as that in the base class

Precautions for virtual function in polymorphism: when rewriting the virtual function of the base class, the virtual function of the derived class can also constitute rewriting without adding the virtual keyword, but this writing method is not very standardized and is not recommended (because the virtual function of the base class is inherited after inheritance, and the virtual function attribute is still maintained in the derived class)

Two exceptions to virtual function rewriting

1. Covariance (different return values):
When a derived class overrides a base class virtual function, the return value type can be different from that of the base class virtual function, but the return value type must be a pointer or reference with inheritance relationship

class A //Base class A
{};

class B : public A //Derived class B
{};
class Vip
{
public:
	virtual A* online() //The return value is a pointer to the base class and must be a pointer to the base class
	{
		cout << "7 Discount" << endl;
		return new A;
	}
};

class Common : public Vip
{
public:
	virtual  B* online()//The return value is a pointer to the derived class and must be a pointer to the derived class
	{
		cout << "No discount at full price" << endl;
		return new B;
	}
};

Operation results:

2. Rewriting of destructors (different names)
If the destructor of the base class is set as a virtual function, the destructor of the display definition in the derived class, whether virtual or not, constitutes an override with the destructor in the base class. Because at the bottom, the compiler makes special treatment for the destructor name, and the name at the bottom is destructor, so it constitutes rewriting

Let's look at the following code

class Vip
{
public:
	virtual void online()
	{
		cout << "7 Discount" << endl;
	}
	~Vip()
	{
		cout << "~Vip" << endl;
	}
};

class Common : public Vip
{
public:
	virtual void online()
	{
		cout << "No discount at full price" << endl;
	}
	~Common()
	{
		if (_name)
		{
			delete[] _name;
			cout << "delete[] _name" << endl;
		}
		cout << "~Common" << endl;
	}
private:
	char* _name = new char[100];
};

Running result: at this time, the resources in the subclass will not be released, resulting in the problem of memory leakage

In order to prevent memory leakage, the destructor must have polymorphic behavior. The destructor in the base class is set as a virtual function, and the destructor and the destructor in the derived class constitute an override

	virtual ~Vip()
	{
		cout << "~Vip" << endl;
	}

Running result: in this way, there will be no memory leakage

Why should destructors be defined as virtual functions?
A: when implementing polymorphism, we point to the subclass object through the base class pointer. When deleting the base class pointer, we want to call the destructor of the subclass first, and then the destructor of the parent class. To achieve this goal, the destructor must be defined as a virtual function, otherwise only the destructor of the parent class will be called, and the destructor of the child class will not be called

override and final in C++11

If we write polymorphic code, due to our negligence, we write the function name, return value or parameters incorrectly, then we cannot constitute rewriting. This kind of error will not be reported during compilation, and can be found only when the program is running. At this time, in order to solve this problem, C++11 introduces two keywords override and final

override
Format: overridden function -- virtual function used in derived class
Function: check whether the virtual function of the derived class overrides a virtual function of the base class. If not, compile and report an error

class Vip
{
public:
	virtual void online() 
	{
		cout << "7 Discount" << endl;
	}
};

class Common : public Vip
{
public:
	//Interface inheritance does not inherit the implementation in the base class. You need to rewrite the implementation yourself
	virtual void online() override
	{
		cout << "No discount at full price" << endl;
	}
};


final
Format 1: class name final -- used in base class
Format 2: function name final -- virtual function used in base class
Function: 1. The class modified by final cannot be inherited; 2. Virtual functions modified by final cannot be overridden

class A final
{};
class B : A
{};

class Vip
{
public:
	//Define inheritance and inherit the implementation of this function from the past and cannot be modified
	virtual void online() final
	{
		cout << "7 Discount" << endl;
	}
};

class Common : public Vip
{
public:
	
	virtual void online()
	{
		cout << "No discount at full price" << endl;
	}
};

Differences and relations among overloading, rewriting and hiding

Function overloading: for several functions with the same name declared in the same scope with different parameters, determine which function to call according to the parameter list, and do not care about the return value of the function
Function hiding: it means that the function of the derived class shields the base class function with the same name. As long as the function with the same name, the base class function will be hidden regardless of whether the parameter list is the same or not
Override override: refers to the existence of redefined functions in derived classes. The function name, parameter list and return value type must be consistent with the overridden function in the base class. Only when the function body is different, the rewritten function of the derived class will be called when the derived class is called, and the rewritten function will not be called. The overridden function in the overridden base class must have a virtual modifier

categoryScopeFunction nameparameter listreturn typeWhether there is virtual modification
function overloadingSame scopeidenticalDifferentNo requirementNo requirement
Function hidingDifferent scopes (parent and child)identicalNo requirementNo requirementParent function cannot have virtua
Override overrideDifferent scopes (parent and child)identicalidenticalSame (except covariance)Parent function must have

abstract class

Definition of pure virtual function: write = 0 after the virtual function, then this function is pure virtual function

//Pure virtual function
virtual void fun() = 0
{}

Definition of abstract class: classes containing pure virtual functions are called abstract classes (also called interface classes)

//abstract class
class A
{
public:
	//Pure virtual function
	virtual void fun() = 0
	{}
};

class B : public A
{
public:
	virtual void fun()
	{
		cout << "B::fun()" << endl;
	}
};

class C : public A
{
public:
	virtual void fun()
	{
		cout << "C::fun()" << endl;
	}
};

Note: 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

class D: public A
{};


Abstract classes are planned. If you want to go to an Internet cafe to surf the Internet, you must provide your ID card, that is, the derived class must provide the virtual function to rewrite the ID card. If you don't provide it, you can't surf the Internet. If you don't rewrite it, you can't use this class

Principle of polymorphism

Virtual function pointer, virtual function, virtual function table pointer

Let's first look at the size in the following classes

class Base
{
public:
	virtual void fun()
	{
		cout << "fun()" << endl;
	}
protected:
	int _a = 1;
};

Operation results:

We found that it is 8 bytes. Why is it 8 bytes? Does the function take up space? Let's create a Base object to see which members are included in this object

We can find that object b contains not only a member variable defined by itself_ a. Well, it contains a pointer of void * * type, so the size is only 8 bytes. Why is there this pointer? That must have something to do with this virtual function. In fact, this pointer is the virtual function table pointer----__ vfptr(v for virtual, f for function, ptr for pointer). Let's take a look at the contents of virtual function table pointer

The virtual function table pointer points to a virtual table - vftable, that is, the virtual table pointer is the first address of the virtual table. The virtual table stores the virtual function pointer, which is the address of the virtual function. So the virtual table is an array of pointers
Let's analyze the relationship between virtual function pointer and virtual function table

class Base
{
public:
	virtual void fun1()
	{
		cout << "fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "Base::fun2()" << endl;
	}
	void fun3()
	{
		cout << "Base::fun3()" << endl;
	}
private:
	int _b = 1;
};

class Derive : public Base
{
public:
	virtual void fun1()
	{
		{
			cout << "Derive::fun1()" << endl;
		}
	}
private:
	int _d = 2;
};

Create Base objects and Derive objects and view their virtual table pointers. We have created two virtual functions in the Base class, and the virtual function pointer will be stored in the virtual table. There are two elements in the virtual table. The first element is the virtual function pointer of fun1 and the second element is the virtual function pointer of fun2

We draw a picture to understand the relationship between virtual table pointer, virtual table, virtual function pointer and virtual function

After understanding the relationship between virtual table pointer and virtual table, let's take a look at the virtual table pointer in the derived class. There is also a virtual table pointer in the derived class. The derived class inherits the virtual table of the base class, that is, copy the virtual table in the base class, and then point to the virtual table with a new virtual table pointer. When a derived class has a virtual function with the same name, return value and parameter list, it will overwrite the same virtual function pointer in the virtual table. Therefore, rewriting can also be called overwriting. That's the reason. For example, fun1 in the derived class overrides fun1 in the base class. At this time, the virtual table in the derived class stores the virtual function pointer of the derived class fun1. Therefore, the reason why polymorphic behavior can occur is that the virtual function pointer in the derived class covers the specified virtual function pointer in the base class, so the virtual function of the derived class will be called when calling

But we should know that the virtual table is not stored in the object. Only the virtual table pointer is stored in the object. We first prove that only virtual table pointers are stored in the object instead of virtual tables. We know that the size of virtual tables is related to the number of virtual functions. Then we create classes with different numbers of virtual functions. If its size is the same, it means that the virtual tables are not stored in the object, and vice versa.

class A
{
public:
	virtual void fun1()
	{
		cout << "fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "fun2()" << endl;
	}
private:
	int _a = 1;
};

class B
{
public:
	virtual void fun3()
	{
		cout << "fun3()" << endl;
	}
private:
	int _a = 2;
};

Running results: we found that even if the number of virtual functions is different, the size of the class is the same, which means that the virtual table is not stored in the class

Let's summarize the location of virtual table pointer, virtual function pointer and virtual function. Virtual table pointer is the starting position or end position stored in an object (different positions on different platforms, generally the starting position); Virtual function pointers are stored in virtual tables; Like ordinary functions, virtual functions are stored in code segments. Where does the virtual table exist?

The memory that our programmers can use includes stack, heap, data segment and code segment. That is, the virtual table must exist in one of the four memories, so how do we judge? We can roughly check the distance of the address, check that the address of the virtual table is similar to that of the memory, and the closest address can be considered to be in that memory

void test()
{
	int a = 10; //Stack
	int* ptr = new int;//heap
	static int s = 1; //Data segment
	const char* str = "123"; //Text constant area / code segment
	cout << "Stack:" << &a << endl;
	cout << "Heap:" << ptr << endl;
	cout << "Data segment:" << &s << endl;
	printf("Code snippet:%p\n", str);
}

Running result: the stack and heap addresses are still very different, and the address difference between data segment and code segment is not small. These four addresses represent different locations in memory. Next, we'll print the address of the virtual table to see which address is close to it

But now the problem is, how do we get the address of the virtual table? Let's analyze it
1. First take the address of b, forcibly convert it into an int * pointer, get the first four addresses of the class, and then get the address of the virtual table pointer;

Base b;
&b;
(int*)&b

2. Then dereference the value and get the value of 4bytes in the head of the b object, which is the pointer to the virtual table, that is, the first address of the virtual table

*(int*)b;

3. However, this value is an int value. We must forcibly convert it to the type stored in the virtual table. The type stored in the virtual table is also the virtual function pointer. The virtual function defined here is a function without parameters and return value. At this time, we get the address of the virtual table

//Virtual function pointer variable
//Pointer with no return value and empty parameter list
typedef void(*vfptr)();
vfptr* vfp = (vfptr*)(int*)&b;

Running result: we can launch

We found that the address of the virtual table is closer to the address of the data segment, so the virtual table is stored in the data segment. Virtual function table is essentially an array of pointers storing virtual function pointers. A nullptr is placed at the end of this array

Implementation principle

How to find virtual functions?
1. Get virtual table pointer from object
2. Find virtual table through virtual table pointer
3. Find the address of the virtual function from the virtual table
4. Instructions for executing virtual functions

Let's look at the execution of the code through assembly

The first line: stored in v is the pointer of v object. Move v to eax
The second line: [eax] is the content pointed to by the eax value. Here, it is equivalent to moving the first four bytes (virtual table pointer) of the vip object to edx
The fifth line: [edx] is the content pointed to by the edx value. Here, it is equivalent to moving the virtual function pointer stored in the first 4 bytes of the virtual table to eax
Line 6: call eax, call virtual function
It can be seen here that the call that meets polymorphism is not determined at compile time, but found in the object after running

Let's get the virtual functions in the virtual table, execute and print them

class Base
{
public:
	virtual void fun1()
	{
		cout << "Base::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "Base::fun2()" << endl;
	}
private:
	int _b = 1;
};

class Derive : public Base
{
public:
	virtual void fun1()
	{
		cout << "Derive::fun1()" << endl;
	}
	virtual void fun3()
	{
		cout << "Derive::fun3()" << endl;
	}
	virtual void fun4()
	{
		cout << "Derive::fun4()" << endl;
	}
private:
	int _d = 2;
};

typedef void(*vfptr)();
void printfVftable(vfptr vtable[])
{
	cout << "Virtual table address:" << vtable << endl;
	//Accessing virtual table elements: virtual function pointers
	vfptr* fptr = vtable;
	while (*fptr != nullptr)
	{
		(*fptr)();
		++fptr;
	}
}

Virtual function table in multi inheritance

In multi inheritance, the virtual table of each class will be inherited

Let's first look at the address of the first virtual table and execute the virtual function in the virtual table

class Base1 {
public:
	virtual void func1() { cout << "Base1::func1" << endl; } 
	virtual void func2() { cout << "Base1::func2" << endl; }
private: int b1;
};

class Base2 {
public:
	virtual void func1() { cout << "Base2::func1" << endl; } 
	virtual void func2() { cout << "Base2::func2" << endl; }
private: int b2;
};

class Derive : public Base1, public Base2 {
public:
	virtual void func1() { cout << "Derive::func1" << endl; } 
	virtual void func3() { cout << "Derive::func3" << endl; }
private: int d1;
};

Running result: func2 function uses the virtual function of the first parent class

Let's look at the address of the second virtual table. How can we get the address of the second virtual table? At this point, we must offset the distance of the size of base1

We can see the virtual function of the second virtual table, and the fun1 function of the subclass also covers the fun1 of the second virtual table, but we find that fun3 only appears in the first virtual table, not in the second virtual table. Therefore, it can be said that the newly defined virtual function pointer will be placed in the first virtual table by default, so the fun3 pointer will only be stored in the first virtual table

Summary of polymorphic fragmentary knowledge

1. The virtual keyword is added only when declared, but not when implemented outside the class
2. static and virtual cannot be used at the same time
3. Static member functions belong to the whole class and cannot be overridden or set as virtual functions. Virtual table pointers exist in objects and cannot be obtained through class names
4. Compile time polymorphism can be realized by function overloading and template; Runtime polymorphism can be realized by virtual functions
5. Different objects of a class share the virtual table of that class
6. Virtual tables are generated during compilation
7. When multiple inheritance occurs, there may be multiple virtual tables
8. Pure virtual functions are not necessarily empty functions, but writing the function body is of little significance
9. The inline function cannot be a virtual function, because the inline function has no address and cannot put the address in the virtual function table
10. If there are virtual functions and virtual inheritance, the first four bytes of the object are still virtual table pointers, followed by virtual base table pointers
11. Constructors cannot be virtual functions. The execution of virtual functions depends on the virtual function table. The virtual function table is initialized in the constructor, that is, initialize vptr to point to the correct virtual function table. During the construction of the object, the virtual function table has not been initialized and cannot be initialized
12. If it is an ordinary object, the speed of calling ordinary functions and virtual functions is the same; If it is a reference or pointer, due to its polymorphism, running and calling a virtual function needs to find it in the virtual function table, and the ordinary function is faster

class Base1 { public: int _b1; }; 
class Base2 { public: int _b2; }; 
class Derive : public Base1, public Base2 { public: int _d; };
int main(){
	 Derive d; 
	 Base1* p1 = &d;  //*p1=_b1
	 Base2* p2 = &d;  //*p2=_b2
	 Derive* p3 = &d;   //*p3=_b1
	 //p1 == p3 != p2
	 return 0;
}
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(int argc ,char* argv[]) {
	B*p = new B;
	p->test(); 
	//B - > 1, func in class A is the real definition
	//In class B, func is rewritten. When the compiler sees that it is rewritten, it does not look at the default value, but only the definition
	return 0;
}

Keywords: C++ pointer Polymorphism abstract class

Added by joePHP on Fri, 18 Feb 2022 13:15:57 +0200