C++: 13. Polymorphism, virtual functions

Polymorphism and virtual functions:

What is a virtual function:

Functions declared with the virtual keyword are virtual functions. The only purpose of virtual functions is to achieve polymorphism (dynamic binding/runtime binding).

The virtual keyword is only used at the member function declaration in the class definition, and cannot be used when writing the member function body outside the class. Functions with the same name in all derived classes that have an overriding relationship will automatically become virtual functions. (For the virtual function converted from the derived class, it is best to also write virtual, is it clear?)

Static member functions cannot be virtual.

To put it simply: functions with virtual declarations are all virtual functions. If there is no virtual, the function of the same name in the derived class will hide the function of the same name in the base class. If so, the function of the same name (same reference and return) of the derived class will overwrite the function of the same name of the base class in the virtual function table.

What is polymorphism:

        Polymorphism can be simply summarized as "one interface, multiple behaviors".

Dynamic binding (run phase) is the basis of polymorphism.

Base class pointer or reference, pointing to a series of derived class objects, calling the overriding method of the same name of the derived class object (that is, the function with the same name, same reference and same return as the base class virtual function), whoever the pointer points to, whose method will be called .

There are two types of polymorphism:

        (1) Compile-time polymorphism (also called static polymorphism): It is mainly realized through function overloading and templates.

        (2) Runtime polymorphism (also called dynamic polymorphism): It is mainly realized through virtual functions.

cover:

       A member function of the base class is a virtual function, and a member function is defined in the derived class. The function name, formal parameters, and return type are all the same as the member function of the base class. Then the virtual function of the base class will be overwritten with the function of the derived class .


Let's talk about how polymorphism is implemented:

A virtual function table vftable is generated during the code compilation phase . When running, it will be loaded into the memory and loaded into the data segment (rodata segment, read-only), which exists in the entire sound life cycle of the program. The virtual function table of a class lists all the virtual function addresses of the class.

If there is a virtual function in the member, there will only be one more virtual function pointer (the first 4 bytes of the object) in the member variable , pointing to the virtual function table vftable() to store the address of the virtual function. The number of virtual functions only affects the size of the table. Does not affect the size of the object.

If the base class has a virtual function, if the derived class has a function with the same name, parameter list and return value, the function of the derived class will automatically become a virtual function, and the derived class will overwrite the original function in the virtual function table.

Supplement: There is also an RTTI (runtime type) pointer in the virtual function table.

打印类型  #include <typeinfo>
cout << typeid(p).name() << endl;   该函数打印p的类型,因为有RTTI才能在继承派生中实现

Take a chestnut:

    #include <iostream>
    using namespace std;
    class A
    {
    public:
        int i;
        virtual void func() {}
        virtual void func2() {}
    };
    class B : public A
    {
        int j;
        void func() {}
    };
    int main()
    {
        cout << sizeof(A) << ", " << sizeof(B);
        return 0;
    }

 Suppose the type of pa is A*, then the execution process of the statement pa->func() is as follows:

1) Take out the first 4 bytes of the position pointed by the pa pointer, that is, the virtual function pointer. If pa points to an object of class A, then this address is the address of the virtual function table of class A;

2) Find the virtual function table according to the virtual function pointer, and find the address of the virtual function to be called in it.

If pa points to an object of class A, the address of A::func will naturally be found in the virtual function table of class A;

Class B does not have its own func2 function, so the address of A::func2 is saved in the virtual function table of class B. In this way, even if pa points to the object of class B, this statement can also be found in the class B during execution pa->func2();. Find the address of A::func2 in the vtable.

3) Call the virtual function according to the address of the found virtual function.

It can be seen from the above process that as long as the statement that calls the virtual function through the base class pointer or base class reference, it must be polymorphic, and the above table lookup process will also be executed, even if the virtual function only exists in the base class , not in derived classes.

There is another one in the virtual function table: RTTI runtime type information
Base *p = new Derive();
cout<<typeid(*p).name()<<endl;


Dynamic binding and static binding:

A binding is a function call.

When in use, a base class pointer is used to point to an object of a derived class. If this virtual function is called, it will first find the virtual function pointer, then find the virtual function table, and then find the virtual function address. And this binding process is dynamic binding (runtime).

If you don't use a pointer to call, but use the object itself to call a function, whether it is a virtual function or not, it is static binding (compile time).

If a pointer is used to call a virtual function, the pointer recognizes the type at runtime; if a general function is called, the pointer recognizes the type at compile time.

  • no virtual -> static binding
  • There are virtual references or pointers -> dynamic binding
  • There is virtual but call with object -> dynamic binding

Pure virtual function:

In general, the base class is not expected to define objects. The base class is just to unify the common attributes.

In order to achieve this goal: a virtual function provided in the base class provides a unified virtual function interface for all derived classes, and the specific implementation is for the derived class to rewrite by itself.

virtual void show() = 0; // Add = 0 after the virtual function is a pure virtual function, no need to implement it.

Pure virtual functions do not actually exist, and pure virtual functions are introduced to facilitate polymorphism.

A class with pure virtual functions is called an abstract class. Abstract classes cannot be instantiated. General base classes should be implemented as abstract classes.

When we don't know which function to define as a pure virtual function, we can define the destructor as a pure virtual function, but it should be noted that the destructor becomes a pure virtual function, and it cannot be implemented in the class. Of course, the compilation will not pass. Solution: It cannot be implemented within the class, I implement it outside the class.

Another point is that if you write a pure virtual function in the base class, but do not write the corresponding base class pure virtual function in the derived class, then due to inheritance, the derived class will also become a pure virtual function.


So here comes the question:

1. When the base class has no more methods, who is implemented as a pure virtual function? --------> Destructor

First of all, it is clear that a function wants to be a virtual function. 1. It must have an address, and it can be placed in the virtual function table if it has an address; 2. It must depend on the object.

1) Can the constructor be a virtual function?

The constructor does not depend on the object. Only after the constructor is executed can there be an object, and only when there is an object can there be a virtual function pointer, so it cannot be a virtual function.

2) Can static member methods be virtual functions? virtual static

Static functions do not depend on objects either, and can be called directly using the class name, nor can they be virtual functions.

3) Can inline functions be virtual functions? => virtual inline  

The inline function is directly expanded in the program, without an address, and cannot be placed in the virtual function table. Nor can it be a virtual function.

4) Can the destructor be implemented as a virtual function?

The destructor depends on the object and has an address. Can be written as a virtual function . We know that if a function is written as a virtual function, then its derived class will have a function with the same name as a virtual function, and the two are in an overriding relationship. But the function name of the destructor is the name of the class plus ~. So the names are different, but actually it's ok because a class will only have one virtual function.

2. When must the destructor be defined as a virtual function?

When the base class pointer points to the derived class object on the heap.

int main()
{
	Base *p = new Derive(20);
	p->show();
	delete p;      如果析构函数不是虚函数的话,调用的时候由于p是基类指针,所以只会调用基类的析构函数,
                       而不会调用派生类的析构函数。导致资源泄露。所以必须将基类的析构函数声明为虚函数。
	return 0;
}

3. Pit 1

class Base
{
public:
	Base(int data) :ma(data){ cout << "Base()" << endl; }
	virtual ~Base() = 0;
	virtual void show(int i=10)
	{
		cout << "Base::show" << endl;
	}
private:
	int ma;
};
Base::~Base()
{
	cout << "~Base()" << endl;
}
///
class Derive : public Base
{
public:
	Derive(int data) :mb(data), Base(data)
	{
		cout << "Derive()" << endl;
	}
	~Derive(){ cout << "~Derive()" << endl; }
	void show(int i=20)
	{
		cout << "Derive::show i:" << i<<endl;
	}
private:
	int mb;
};

int main()
{
	const int a = 10;      用const进行类比,我们知道const定义的变量的值是不能修改的,直接修改会报错。
	int *q = (int*)&a;        但通过指针修改地址的值,还是可以将其更改。
	*q = 20;     原因就是,编译的时候确实没有检测出来修改,但运行的时候就可以将他改了。
        //
	Base *p = new Derive(20);  
	p->show();  
        结果:打印出来的是Derive::show i:10。诶呦奇怪了,我派生类定义的明明是20,为啥调的是派生类的函数,打印出来的却是10?
        解释:由于参数的压栈,是编译阶段确定的。具体调用哪个方法,是经过动态绑定,运行时才确定。
        所以可能导致使用的是基类的参数,而调用的是派生类的函数。(所以虚函数最好不要写默认值,写的话就写成一样的)
	delete p;
	return 0;
}

Asking a question: How to call a private member function of a derived class?
It is the same as const. Since the access rights of the method are determined during the compilation phase. So if the function corresponding to the base class is written as a virtual function, then it is dynamically bound when using the method, and which method to use is determined at runtime, so the private member function of the derived class can be called.

4. Pit 2

class Animal
{
public:
	Animal(string name) :_name(name){}
	virtual void bark() = 0;
protected:
	string _name;
};

class Cat : public Animal
{
public:
	Cat(string name) :Animal(name){}
	virtual void bark()
	{
		cout << _name << " miao miao!" << endl;
	}
};

class Dog : public Animal
{
public:
	Dog(string name) :Animal(name){}
	virtual void bark()
	{
		cout << _name << " wang wang!" << endl;
	}
};
int main()
{
	Animal *p1 = new Cat("猫");
	Animal *p2 = new Dog("狗");

	int *pp1 = (int*)p1;
	int *pp2 = (int*)p2;

	int tmp = pp1[0];将两个虚函数指针交换
	pp1[0] = pp2[0];
	pp2[0] = tmp;

	p1->bark();   猫 vfptr  ->   狗vftable
	p2->bark();   导致猫叫出了狗的声音

	delete p1;
	delete p2;

	return 0;
}

5. In the constructor, call the virtual function, is it static binding or dynamic binding?

Dynamic binding (polymorphism) does not happen inside neither destructors nor constructors. As mentioned earlier, there must be an object to call a virtual function, and the life cycle of the object is after the end of the constructor until the beginning of the destructor. So no dynamic binding occurs inside construction and destruction.

6. Looking at a question:

class Base
{
public:
	Base(int data) :ma(data)
	{ 
		// 1. 栈帧开辟  2.栈帧初始化 3.vftable=》vfptr里面
		cout << "Base()" << endl; 
		clear();
		this->show();
	}
	virtual ~Base()
	{
		this->show();
		cout << "~Base()" << endl;
	}
	void clear()
	{ 
                memset(this, 0, sizeof(*this)); 
        }
	virtual void show(int i=10)
	{
		cout << "Base::show" << endl;
	}
private:
	int ma;
};
///
class Derive : public Base
{
public:
	Derive(int data) :mb(data), Base(data)
	{
		cout << "Derive()" << endl;
	}
	~Derive()
	{ 
		cout << "~Derive()" << endl; 
	}
	void show(int i=10)
	{
		cout << "Derive::show i:" << i<<endl;
	}
private:
	int mb;
};
int main()
{
        帧的开辟,栈帧的初始化  将虚函数表的地址写入虚函数指针中。都是进入构造函数一开始时进行的,
        如果清空了虚函数指针,在动态绑定的时候指向基类虚函数对象的指针会报错。
	Base *p1 = new Base(10);
	p1->show();
	delete p1;

	Base *p2 = new Derive(10);
	p2->show();
	delete p2;
	// 继承结构中,每一层构造函数都会把自己类型的虚函数表的地址,写到vfptr里面

	return 0;
}

The development of the stack frame, the initialization of the stack frame Write the address of the virtual function table into the virtual function pointer. It is all done at the beginning of entering the constructor. If the virtual function pointer is cleared, the pointer to the base class virtual function object will report an error during dynamic binding.

I have written a bit too much, but I haven't finished it yet. The follow-up content will be in the next article.

Guess you like

Origin blog.csdn.net/qq_41214278/article/details/83996143