C++ virtual virtual function

1, Definition

Simply put, those member functions modified by the virtual keyword are virtual functions. The function of virtual function, explained in professional terms, is to realize Polymorphism, which is to separate the interface from the implementation; Using vivid language to explain is to achieve common methods, but adopt different strategies due to individual differences.

First look at the first code

#include <iostream>

using namespace std;

class A
{
    public:
        void print()
        {
            cout<<"This is A"<<endl;
        }
};
 
class B : public A
{
    public:
        void print()
        {
            cout<<"This is B"<<endl;
        }
};

int main()
{
    A a;
    B b;
    a.print();
    b.print();
    return 0;
}

Operation results:

If you change the main function:

int main()
{
    A a;
    B b;
    A *p1 = &a;
    A *p2 = &b;
    p1->print();
    p2->print();
    return 0;
}

Operation results:

Although p2 points to the class B object, it still calls the print of class A.
If you add virtual to A's print

#include <iostream>

using namespace std;

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

int main()
{
    A a;
    B b;
    A *p1 = &a;
    A *p2 = &b;
    p1->print();
    p2->print();
    return 0;
}

Operation results:

At this time, not only class A has become a virtual function, but its derived class class B has also become a virtual function, but it can be written in the code. If it is not written, the compiler will add it automatically.

2, Principle

How virtual functions call their corresponding functions according to different objects:

class A{//Virtual function sample code
    public:
        virtual void fun(){cout<<1<<endl;}
        virtual void fun2(){cout<<2<<endl;}
};
class B : public A{
    public:
        void fun(){cout<<3<<endl;}
        void fun2(){cout<<4<<endl;}
};

Because there are virtual functions in these two classes, the compiler will insert a piece of data you don't know for them and create a table for them. That data is called vptr pointer, pointing to that table. That table is called vtbl. Each class has its own vtbl. The function of vtbl is to save the address of the virtual function in its own class. We can visualize vtbl as an array. Each element of this array stores the address of the virtual function.

A *p = new B;
p -> fun();

The above code undoubtedly calls B::fun.

#include<iostream>
using namespace std;
//Add the above "virtual function sample code" here
void CallVirtualFun(void*pThis,intindex=0)
{
    void(*funptr)(void*);
    long lVptrAddr;
    memcpy(&lVptrAddr,pThis,4);
    memcpy(&funptr,reinterpret_cast<long*>(lVptrAddr)+index,4);
    funptr(pThis);//call
}
int main()
{
    A *p = new B;
    CallVirtualFun(p);//Call virtual function p - > fun()
    CallVirtualFun(p,1);//Call virtual function p - > fun2()
    system("pause");
    return 0;
}

When compiling, the compiler will perform a process similar to the above code:
First, new B applies for the address of a memory unit from the free storage area of memory (memory is divided into five areas: global namespace, free storage area, register, code space and stack), and then implicitly saves it in a pointer Then assign this address to the pointer P of type A, which is used to store the address taken from vtbl.
Memcpy (& lVptrAddr, PThis, 4) is to save the address of vtbl (i.e. the address of fun) in lVptrAddr;
Memcpy (& funptr, reinterpret_cast < long * > (lVptrAddr) + index, 4) saves the address of fun2 (vtbl of fun2) to the back of lVptrAddr (similar to array).
In this way, when we call the virtual function, we actually call the code to obtain the address of the virtual function, so the efficiency of the virtual function is low.

3, Usage

Transfer from this address
1. Polymorphism
As mentioned earlier, in a common way, but due to individual differences, different strategies are adopted.

#include<iostream> 
using namespace std;
class A
{
public:
    virtual void display(){ cout<<"A"<<endl; }
};
class B : public A
{
public:
    void display(){ cout<<"B"<<endl; }
};
void doDisplay(A *p)
{
    p->display();
    delete p;
} 
int main(int argc,char* argv[])
{     
    doDisplay(new B());
    return 0;
}

The printed result of this code is B, but when the virtual in class A is removed, the printed result is a. When there is no virtual in the base class, the compiler regards p as a class a object when compiling, and naturally calls class a methods. However, after adding virtual, the dispaly method is changed into a virtual method. In this way, when calling, the compiler will see whose instantiated object is called, so as to achieve the effect of polymorphism. In other words, when there is a virtual method that has overridden the base class in the derived class of the base class, use the pointer of the base class to point to the object of the derived class, and calling this method will actually call the method finally implemented by the derived class.
2. Virtual inheritance: solve data redundancy

#include<iostream>
using namespace std;
class Person
{
public: 
	Person(){ cout<<"Person structure"<<endl; }
	~Person(){ cout<<"Person Deconstruction"<<endl; }
};
class Teacher : virtual public Person
{
public: 
	Teacher(){ cout<<"Teacher structure"<<endl; }
	~Teacher(){ cout<<"Teacher Deconstruction"<<endl; }
};
class Student : virtual public Person
{
public: 
	Student(){ cout<<"Student structure"<<endl; }
	~Student(){ cout<<"Student Deconstruction"<<endl; }
};
class TS : public Teacher, public Student
{
	public: TS(){ cout<<"TS structure"<<endl; }
	~TS(){ cout<<"TS Deconstruction"<<endl; }
};
int main(int argc,char* argv[])
{
    TS ts;
    return 0;
}

Operation results:

If you remove the virtual of the Person class:

You can clearly see that this result is obviously not what we expected. When constructing TS, we need to construct its base class first, that is, Teacher class and Student class. The Teacher class and Student class are both inherited from the Person class. This leads to the instantiation of two Person classes when constructing TS. Similarly, when destructing, the Person class is also destructed twice, which is very dangerous, which leads to the third usage of virtual, virtual destruct.
3. Virtual deconstruction

#include<iostream>
using namespace std;
class Person
{
public:
	Person() {name = new char[16];cout<<"Person structure"<<endl;}
	virtual ~Person() {delete []name;cout<<"Person Deconstruction"<<endl;}
private:
	char *name;
};
class Teacher :virtual public Person
{
public: 
	Teacher(){ cout<<"Teacher structure"<<endl; }
	~Teacher(){ cout<<"Teacher Deconstruction"<<endl; }
};
class Student :virtual public Person
{
public: 
	Student(){ cout<<"Student structure"<<endl; }
	~Student(){ cout<<"Student Deconstruction"<<endl; }
};
class TS : public Teacher,public Student
{
public: 
	TS(){ cout<<"TS structure"<<endl; }
	~TS(){ cout<<"TS Deconstruction"<<ENDL; }
};
int main(int argc,char* argv[])
{
	Person *p = new TS();
	delete p;
	return 0;
}

Operation results:

Person structure
Teacher structure
Student structure
TS structure
TS Deconstruction
Student Deconstruction
Teacher Deconstruction
Person Deconstruction

However, when we remove the virtual in front of the destructor in the Person class, the running result is:

Person structure
Teacher structure
Student structure
TS structure
Person Deconstruction
 Program crash

Obviously, this result is not the program we want, and the consequences of crash are unpredictable. Therefore, we must pay attention to adding virtual in front of the destructor of the base class to make it virtual destructor. Using virtual functions in C + + programs, virtual inheritance and virtual destructor are good habits, which can avoid many problems.

Tags: C++ Polymorphism

Posted by jorgep on Fri, 20 May 2022 09:51:12 +0300