Implementation details of virtual functions in c++

  • 2020-05-10 18:34:04
  • OfStack

preface

c++ is divided into compile-time polymorphism and run-time polymorphism. Run-time polymorphism depends on virtual functions, and most of you have probably heard that virtual functions are implemented by a table of virtual functions plus a pointer to a virtual function, but is that really the case? While the c++ specification has complex language details, the underlying implementation mechanism is left to the imagination of compiler vendors. (perhaps some special processor circuit structure is built to support virtual functions, perhaps the processor is not a von neumann type at all, or perhaps some future vendor has invented a more efficient data structure than a table of virtual functions.)

Virtual function table

Encapsulation combines the data and operations of an instance in 1, but the instance itself has only data, no functions, and is Shared with the functions of a class. Let's indirectly prove this point by an example


class Base1
{
public:
 int a;
 void func() { cout << "heel" << endl; }
};

Base1 b1;
cout << sizeof(b1) << endl;

print


4

If there are virtual functions in the class, a pointer to a virtual function is added to the object, which points to a table of virtual functions where the addresses of each virtual function are located.


+--------+    +---------+
| pvtbl |------>| vfunc1 |
+--------+    +---------+
| data1 |    | vfunc2 |
+--------+    +---------+
| ...  |    | ...   |

When a subclass inherits a parent class, it overrides each item in the vtable in turn, and if the subclass does not override an item, it retains it. When an object is instantiated, a virtual function pointer exists in the instance as a hidden data. If the ordinary member function is called through the superclass pointer, the ordinary function and the type are bound at 1, so the superclass member function will still be called. If a virtual function is called through a superclass pointer, the virtual function table (that is, the virtual function table of the subclass) is found through the object's virtual pointer, and the virtual function item is located to achieve polymorphism.

Is it simple? c++ implements high-level abstractions in this seemingly primitive way. That's what the compiler does in general, and that's what the Visual Studio 2013 compiler does. To improve performance, VS makes sure that the virtual pointer is at the front of the object instance. (historically, there have been compilers that don't do this, like Borland?) .

Implementation in Visual Studio 2013

Here's an example (I can write this because I know the memory layout of Visual Studio 2013 compiled objects)


#include <iostream>
using namespace std;

class Base 
{
public:
 typedef void (*func)();
 virtual void func1() { cout << "Base::func1" << endl; }
 virtual void func2() { cout << "Base::func2" << endl; }
 virtual void func3() { cout << "Base::func3" << endl; }
};

class Derived: public Base
{
public:
 virtual void func1() { cout << "Derived::func1" << endl; }
 virtual void func3() { cout << "Derived::func3" << endl; }
};

int main()
{
 Base b, b1;
 int** pvirtualtable1 = (int**)&b;
 cout << "Base object vtbl address: " << pvirtualtable1[0] << endl;
 int** pvirtualtable11 = (int**)&b1;
 cout << "another Base object vtbl address: " << pvirtualtable11[0] << endl;
 cout << "function in virtual table" << endl;
 for (int i = 0; (Base::func)pvirtualtable1[0][i] != NULL; ++i)
 {
 auto p = (Base::func)pvirtualtable1[0][i];
 p();
 }
 cout << endl;

 Derived d;
 int** pvirtualtable2 = (int**)&d;
 cout << "Derived object vtbl address: " << pvirtualtable2[0] << endl;
 cout << "function in virtual table" << endl;
 for (int i = 0; (Base::func)pvirtualtable2[0][i] != NULL; ++i)
 {
 auto p = (Base::func)pvirtualtable2[0][i];
 p();
 }
 cout << endl;
}

print


Base object pvtbl address: 0029DA58
another Base object pvtbl address: 0029DA58
function address in virtual table
Base::func1
Base::func2
Base::func3

Derived object pvtbl address: 0029DB20
function address in virtual table
Derived::func1
Base::func2
Derived::func3

As you can see, the vtable for different instances of type 1 is the same. After inheritance, the subclass has its own vtable with corresponding updates (Derived::func1, Derived::func3), and the unoverridden items in the table remain as their original values (Base::func2).

conclusion

The above is the whole content of this article, I hope the content of this article to your study or work can bring 1 definite help, if you have questions you can leave a message to communicate.


Related articles: