Virtual function, virtual inheritance, polymorphism and virtual function table of c + + Notes

reference resources: C + + - virtual function, virtual inheritance, polymorphism and virtual function table - Zhihu (zhihu.com)

1. What is a virtual function?

Virtual function is a kind of internal function modified by virtual keyword, which can be divided into virtual function and pure virtual function. Let's take a look at the code first:

#include<iostream>
#include<memory>

class A
{
public:
    virtual void func(){std::cout<<"A func() called"<<std::endl;}

};

class B:public A
{
public:
    void func(){std::cout<<"B func() called"<<std::endl;}


};



int main()
{

    A a;
    a.func();

    B b;
    b.func();


    return 0;
}

Operation results:

ok, let's take a look at the role of the virtual keyword here. Class B inherits from Class A, but class B has a func function with the same name as a. at this time, if an object of class B is declared, it can correctly call B's func.

You may have a question at this time. I don't see what role the virtual keyword plays here?

Let's remove the virtual of class A and look at the output:

Output is:

you 're right! At this time, it is found that whether the virtual keyword is removed or not does not change the output result... It seems that virtual doesn't play any role here.

We'll talk about why we should modify the method that needs to be rewritten with virtual later. Now you think it's useless first!

First, let's add a knowledge point: destructors can be written as virtual, but constructors can't.

Why? The reason is complicated. In short, the virtual function is realized through a special function. It is stored in the memory space where the class is located. The constructor is generally used to apply for memory. There is no memory. How can we find this special function?

So the constructor cannot be virtual. Of course, there are other reasons. For specific reasons, please refer to the following articles:

Why can't constructors be virtual functions

OK, now let's try to write the destructor virtual and see what happens?

#include<iostream>
#include<memory>

class A
{
public:
    A(){std::cout<<"A() called"<<std::endl;}
    virtual ~A(){std::cout<<"~A() called"<<std::endl;}

};

class B:public A
{
public:
    B(){std::cout<<"B() called"<<std::endl;}
    ~B(){std::cout<<"~B() called"<<std::endl;}


};



int main()
{

    B b;


    return 0;
}

The operation results are as follows:

OK, so let's take a look at what's the use of virtual here? You can try to remove virtual and see if the output is different.

The conclusion is that there is no difference. The output is the same whether the destructor virtual of the base class is or not.

Ah, what's the use of adding a dummy to the destructor! Now you think it's useless

Let's talk about pure virtual functions.

The previous example program will:

virtual void func(){std::cout<<"A func() called"<<std::endl;}

Amend to read:

virtual void func()=0;

In this way, func of class A is a pure virtual function. At this time, we compile again, and the following errors occur:

#include<iostream>
#include<memory>

class A
{
public:
    virtual void func()=0;
};

class B:public A
{
public:
    void func(){std::cout<<"B func() called"<<std::endl;}

};


int main()
{

    A a;
    a.func();

    B b;
    b.func();

    return 0;
}

Variable type A is an abstract class (because all classes containing pure virtual functions are abstract classes), and the pure virtual method func that cannot be executed in abstract class A; Cannot declare an instance object for abstract class a!!!

It is equivalent to the following:

error C2259: "A": Abstract classes cannot be instantiated
note: Due to the following members:
note: "void A::func(void)": It's abstract
note: See“ A::func"Statement of

Yes! Pure virtual functions cannot be called because they have no concrete implementation and only declaration. Therefore, a.func(); such code will report an error.

So let's change the code to the following:

#include<iostream>
#include<memory>

class A
{
public:
    virtual void func()=0;

};

class B:public A
{
public:
    void func(){std::cout<<"B func() called"<<std::endl;}


};



int main()
{
    B b;
    b.func();


    return 0;
}

The operation results are as follows:

be careful:

(1) OK, then we know that A pure virtual function is A function that does not need to write an implementation, but only needs to write A declaration. It is left to the derived class to implement its specific details. Here, we call A as the base class and B as the derived class, the same below.

(2)   In addition, we need to pay extra attention because class A has pure virtual functions. Therefore, we also call class a abstract class and A::func() abstract functions.

(3) Remember, abstract classes cannot be instantiated, that is, A a; this syntax is illegal and wrong.

The question comes again. Can derived classes be abstract classes?

We might as well try:

#include<iostream>
#include<memory>

class A
{
public:
    virtual void func()=0;

};

class B:public A
{
public:
    void func()=0;


};

class C:public B
{
  public:
    void func(){std::cout<<"C func() called"<<std::endl;}
};



int main()
{
    C c;
    c.func();


    return 0;
}

The operation results are as follows:

From this result, we can find that under the premise of single inheritance, you only need to instantiate a derived class that is not an abstract class, and an abstract class can inherit from an abstract class, and it can be inherited by another class.

2. What is virtual inheritance?

The code is as follows:

#include<iostream>
#include<memory>

class A
{
public:
    int a;

};

class B:public A
{
public:
    int b;


};

class C:public A
{
  public:
    int c;
};

class D:public B, public C
{
public:
    int d;
};


int main()
{
    D d;
    d.a = 5;


    return 0;
}

result:

main.cpp(26): error C2385: ambiguous access to "a"
main.cpp(26): note: may be "a" (in base "a")
main.cpp(26): note: may also be "a" (in base "a")
main.cpp(27): error C2385: ambiguous access to "a"
main.cpp(27): note: may be "a" (in base "a")
main.cpp(27): note: may also be "a" (in base "a")

The last three and the first three reported the same mistakes.

After the class D is instantiated, when object D wants to access member a of base class A, it doesn't know whether to find it through B or C.

This is called diamond inheritance, as shown in the figure:

How can we access member a of a passing through B at this time? The following code gives the solution:

#include<iostream>
#include<memory>

class A
{
public:
    int a;

};

class B:public A
{
public:
    int b;


};

class C:public A
{
  public:
    int c;
};

class D:public B, public C
{
public:
    int d;
};


int main()
{
    D d;
    d.B::a = 5;
    std::cout<<d.B::a<<std::endl;

    return 0;
}

The operation results are as follows:

OK, we finally found a member method that accesses the master class (the base class of the base class) through the specified class!

Then I raise a new question: is a accessed by B and a accessed by C an a?

We'll know by doing an experiment. Therefore, we give the following code:

#include<iostream>
#include<memory>

class A
{
public:
    int a;

};

class B:public A
{
public:
    int b;


};

class C:public A
{
  public:
    int c;
};

class D:public B, public C
{
public:
    int d;
};


int main()
{
    D d;
    std::cout<<&d.B::a<<std::endl;
    std::cout<<&d.C::a<<std::endl;

    return 0;
}

If the addresses are different, it must not be an a, but their address positions differ by 8. Is this a coincidence?

As we will talk later, it can be proved that their offset will not change with the number of code execution times in this example.

In fact, that is to say, if it is inherited in this way, there will be two copies of A in D.

This is not right. We should only want an A. at this time, we need to introduce virtual inheritance. Add the virtual keyword before the base class to modify the base class to make it a virtual base class. See the code below:

#include<iostream>

class A
{
public:
	int a;

};

class B :virtual public A
{
public:
	int b;
};

class C :virtual public A
{
public:
	int c;
};

class D :public B, public C
{
public:
	int d;
};

int main()
{
	D d;
	std::cout << &d.a << std::endl;
	std::cout << &d.B::a << std::endl;
	std::cout << &d.C::a << std::endl;
	
	return 0;
}

The operation results are as follows:

We can find that a will only have a copy in d, regardless of the class that does not specify passing.

It is given in the original text, but remember and pay attention to: put the code in the above code

class D :public B, public C

Write it all

class D :virtual public B, virtual public C

It is not possible to implement multi inheritance!!!!!

However, I found that it can be compiled and run normally after modification. The code is as follows:

#include<iostream>

class A
{
public:
	int a;

};

class B :virtual public A
{
public:
	int b;
};

class C :virtual public A
{
public:
	int c;
};

class D :virtual public B, virtual public C
{
public:
	int d;
};

int main()
{
	D d;
	std::cout << &d.a << std::endl;
	std::cout << &d.B::a << std::endl;
	std::cout << &d.C::a << std::endl;
	
	return 0;
}

3. Polymorphism

Let's solve the problem raised in the first section. What is the role of adding virtual to the member function / destructor in the base class?

Let's take a look at how C + + implements polymorphism. See the following code. The code gives an example of a base class object calling a method in a derived class:

#include<iostream>


class Base
{
public:

	virtual void func() { std::cout << "Base func() called" << std::endl; }
};

class Derived :public Base
{
public:

	void func() { std::cout << "Derived func() called" << std::endl; }
};

int main()
{

	Base *b = new Derived;
	b->func();
	return 0;
}

It can be found that giving the pointer of the Base class the attribute of the derived class can actually call the methods in the derived class correctly!

What if we delete the virtual of the Base class?

The output will become:

We can find that at this time, the method in the derived class will not override the method with the same name in the base class, so the method of the derived class cannot be called.

Similarly, we can guess what happens if the destructor of the Base class is not virtual?

#include<iostream>


class Base
{
public:
	Base() { std::cout << "call Base Class constructor" << std::endl; }
	~Base() { std::cout << "call Base Class destructor" << std::endl; }
	
};

class Derived :public Base
{
public:
	Derived() { std::cout << "call Derived Class constructor" << std::endl; }
	~Derived() { std::cout << "call Derived Class destructor" << std::endl; }

	
};

int main()
{

	Base *b = new Derived;
	delete b;
	return 0;
}

We can see the results and draw an analytical conclusion:

1)Base *b = new Derived; That is, the constructor of the base class and the constructor of the derived class are called. Note that base * B is the base class pointer B applied in the stack memory, and new derived is the pointer of the derived class applied in the heap memory.

2)Base b; This will only call the constructor of the base class, without involving the Derived class.

3) From the results, it is found that the space of Derived class is not destructed, that is, it is not released, that is, there is a memory leak (in fact, the leak here is not rigorous, because everything will be recycled by the system after the end of the whole program, so there is no so-called memory leak

Note: if the destructor of Base class is set to virtual, there are:

#include<iostream>


class Base
{
public:
	Base() { std::cout << "call Base Class constructor" << std::endl; }
	virtual ~Base() { std::cout << "call Base Class destructor" << std::endl; }
	
};

class Derived :public Base
{
public:
	Derived() { std::cout << "call Derived Class constructor" << std::endl; }
	~Derived() { std::cout << "call Derived Class destructor" << std::endl; }

	
};

int main()
{

	Base *b = new Derived;
	delete b;
	return 0;
}

In this way, all the requested memory can be released successfully!!!

OK, we have figured out the virtual characteristics of the destructor, but we still haven't figured out a problem:

If the function is declared as virtual, why can the Base pointer find the correct method after binding the properties of the derived class?

Next, I will talk about virtual function table and virtual function table pointer, which are necessary tools to realize polymorphism.

At the same time, we will discuss memory distribution, and we understand why we can't assign methods in the base class to derived class pointers through the virtual function table.

4. Virtual function table and virtual function table pointer

Let's start with a non polymorphic code to explore the virtual function table and its pointers. See the following code:

#include<iostream>


class Base
{
public:
	Base() { std::cout << "call Base Class constructor" << std::endl; }
	virtual ~Base() { std::cout << "call Base Class destructor" << std::endl; }
	virtual void func() { std::cout << "call Base Class func()function." << std::endl; }
	
};

class Derived :public Base
{
public:
	Derived() { std::cout << "call Derived Class constructor" << std::endl; }
	~Derived() { std::cout << "call Derived Class destructor" << std::endl; }
	void func() { std::cout << "call Derive Class func()function" << std::endl; }

	
};

int main()
{

	Base *b = new Base;
	Derived* d = new Derived;
	delete b;
	delete d;
	return 0;
}

At this time, let's turn on monitoring to see what class objects have:

Where, the address of b is 0x0000027ae0af2c30, in which there is a hidden variable _vfptr, the type is void * *, and the address is 0x00007ff63928bc30.

The address of d is 0x00000149f5142570, in which there is a Base class. Under the Base class, there is a hidden variable _vfptr, the type is void * *, and the address is 0x00007ff63928bcb0.

So what does this _vfptr point to? From its type void * *, we can know that it should point to a pointer of void * type, that is, void * * is a pointer to a pointer, that is, a two-dimensional pointer, which can also be regarded as a two-dimensional array. It is our virtual function table. Let's just look at b and open _vfptr of b to expand:

We can see that there are two data in it. One ([0]) is the address of the destructor of Base, 0x00007ff639281230; the other ([1]) is the func of Base, 0x00007ff6392814a1.

This void * * actually stores the actual storage addresses of all functions modified by the virtual keyword. Void * is a pointer, and its value is an address.

Let's look at the name of the variable pointed to by _vfptr: project15.exe!void(*Base::`vftable`[3]) ()

It is a function pointer array called 'vftable' (i.e. void * * type) (pointer array: each element of the array is a pointer, refer to Blog ), the length is 3. (why is the length 3?)

OK, this virtual function table will reveal the truth.

Next, we will change the code that does not show polymorphism to show polymorphism. The specific code is as follows:

#include<iostream>

class Base
{
public:
	Base() { std::cout << "Call base class Base Constructor for" << std::endl; }
	virtual ~Base() { std::cout << "Call basic Base Destructor for" << std::endl; }
	virtual void func() { std::cout << "Call base class function func" << std::endl; }
};

class Dervied :public Base
{
public:
	Dervied() { std::cout << "Call derived class Dervied Constructor for" << std::endl; }
	~Dervied() { std::cout << "Call derived class Derivied Destructor for" << std::endl; }
	void func() { std::cout << "Call derived class functions func Destructor for" << std::endl; }

};



int main()
{
	Base* b = new Base();
	Dervied* d = new Dervied();
	delete b;
	delete d;

	b = new Dervied();
	delete b;
	
	return 0;
}

Now let's look at the state of pointer variable b:

We can find that b at this time is a pointer to the Base class, but _vfptr points to the virtual function table of the Dervied class. In addition, we find its address 0x00007ff65629bca8, which is the same as that of d last time (as shown in the figure above)

__vfptr saves the same address value.

Well, that's good. When I call a function (for example, func), do I go to the virtual function table to find the function address? Then I can find the method in the derved class I want to call as soon as I find it? We also know that in the whole life cycle of the program,

The virtual function table of each class has a unique address.

Back to the previous question, why can't we assign the attribute of the base class to the pointer of the derived class? Let's take an example, as shown in the following code example:

#include<iostream>

class Base
{
public:
	Base() {}
	virtual ~Base() {}
	virtual void func() { std::cout << "Call base class function func" << std::endl; }
	virtual void func2() { std::cout << "Call base class function func2" << std::endl; }
};

class Dervied :public Base
{
public:
	Dervied() {}
	~Dervied() {}
	void func() { std::cout << "Call derived class func function" << std::endl; }
	virtual void func3() { std::cout << "Call derived class func3 function" << std::endl; }

};



int main()
{
	Base* b = new Base();
	Dervied* d = new Dervied();
	delete b;
	delete d;
	
	return 0;
}

Let's look at the virtual function table of b and d:

It can be found that the length of the virtual function table of b is 4, while the length of the virtual function table of d is 5.

That is to say, the Dervied class object pointer should have received a virtual function table with a length of 5, but you passed him a Base class attribute. The whole Base class only has a virtual function table with a length of 4, and there is no way to fill the virtual function table with a length of 5.

That is to say, without the implementation of a method, which method is missing?

The answer is: func3() is missing

Suppose I have a statement:

Dervied *d = new Base();

Assuming that this statement is legal, we will call Derived func3() method:

d->func3();

Remember: even if you assign the attribute of Base class to d, d itself is still a pointer to Derived class, and the compiler will not care what attribute you assign to d. therefore, d - > func3(); this statement should be legal.

At that time, the program jumps to the virtual function table of d (note that this virtual function table is the virtual function table of Base class). It finds that the func3() method cannot be found, so it crashes.

 

      Because the derived class inherits all the public virtual functions of the base class, the derived class is a superset of the base class (the opposite is a subset). Therefore, it is legal to assign the derived class attribute to the base class, but it must be illegal to assign the base class attribute to the derived class, because the base class lacks some newly defined attributes of the derived class (that is, the newly defined members of the derived class cannot be found in the base class) .

What about typing virtual for the function of the private attribute of the base class?

You can try. There is no way to override these virtual methods or access them in derived classes. It is also meaningless.

 

Fundamentals: inheritance, polymorphism and virtual functions - Zhihu (zhihu.com)

(30 messages) detailed explanation of C + + polymorphic virtual function table (multiple inheritance and multiple inheritance) Qingchengshan little monk CSDN blog multiple inheritance virtual function table

(30 messages) C + + (thorough) virtual function polymorphism abstract class principle of polymorphism dodamce blog - CSDN blog

C + + virtual function table (implementation principle of polymorphism) (biancheng.net)

Virtual function, virtual function table, virtual inheritance_ Fiona_ Sina blog (sina.com.cn)

Why doesn't C + + set all functions to virtual functions- Zhihu (zhihu.com)

Excuse me, this c + + multi inheritance problem- Zhihu (zhihu.com)

(1 message) what is the function of c + + virtual function- Zhihu (zhihu.com)

12. Virtual function and polymorphism Zhihu (zhihu.com)

(2 messages) why did C + + create a virtual table- Zhihu (zhihu.com)

C + + foundation - understanding of dynamic polymorphism - Zhihu (zhihu.com)

(virtual inheritance) interesting operation to prevent duplicate content - Zhihu (zhihu.com)

Implementation principle of c + + polymorphism and virtual function table - Zhihu (zhihu.com)

(2 messages) principle of polymorphic implementation - principle analysis of virtual function table, full of dry goods, object-oriented characteristics - Zhihu (zhihu.com)

In depth analysis of virtual function table of C + + multiple inheritance - Zhihu (zhihu.com)

C + + polymorphism - Zhihu (zhihu.com)

Keywords: C++

Added by Envex on Fri, 29 Oct 2021 12:58:53 +0300