Master C++ virtual function table in ten minutes

C++ realizes polymorphism, and very important concepts are virtual table pointers and virtual tables. Polymorphism means that the parent class (base class, interface class) points to the subclass (derived class, implementation class) object, which is one of the basic characteristics of object-oriented design.
ps: All examples in this article, run in x64 environment, compiler g++ -std=c++11

  1. Start to understand virtual pointers from a simple example
class Base {
    
    
   public:
    Base() {
    
    }
    ~Base() {
    
    }
    virtual void function() {
    
    }
    //virtual void function2() {}
};

sizeof(Base) We find that the result is 8, which is actually the length of a pointer.
So what if it is two virtual functions? The answer is also 8. What actually occupies this memory is the virtual table pointer _vptr.

int main() {
    
    
    Base b;
    cout << sizeof(Base) << endl;//8
    cout << sizeof(b._vptr) << endl;//8
    cout << b._vptr << endl;
    cout << typeid(b._vptr).name() << endl;
    return 0;
}

The virtual table pointer points to a virtual table in which the virtual functions in the class are stored.
If there is a virtual function in the class, when constructing the object, a virtual table pointer will be generated to point to the virtual table, and the virtual function will be stored in the virtual table.
Therefore, it is important to note that do not call virtual functions in the constructor of the class. This is a dangerous action , because it is likely that it has not been initialized yet.

  1. Virtual table pointers in
    subclasses Will subclasses inherit the virtual table pointers of the parent class? The answer is yes, _vptr will be inherited like a hidden pointer.
class Derived : public Base {
    
    
   public:
    virtual void function() {
    
    }
    virtual void function3() {
    
    }
};
int main() {
    
    
    Derived d;
    if (d.Base::_vptr == d._vptr) {
    
    
        std::cout << "yes" << std::endl;  // yes
    }
    return 0;
}

What if it is multiple inheritance? Subclasses inherit multiple virtual table pointers. As we can see through the following example, _vptr inherits from the first base class.

class Base2 {
    
    
   public:
    virtual void function4() {
    
    }
};

class Derived : public Base, public Base2 {
    
    
   public:
    virtual void function() {
    
    }
    virtual void function3() {
    
    }
};
int main() {
    
    
    Derived d;
    Derived d1;
    if (d.Base::_vptr == d._vptr) {
    
    
        std::cout << "yes" << std::endl;  // yes
    }
    if (d1._vptr == d._vptr) {
    
    
        std::cout << "yes" << std::endl;  // yes
    }

    cout << sizeof(d) << endl;  // 16
    return 0;
}

_vptr inherited from the first base class? ? ? ? This statement is not rigorous . As shown above, in fact, the contents of _vptr of d and d1 are the same, and all Derived objects share the address pointed to by _vptr, that is, the virtual function table .

In addition, where is the virtual table pointer _vptr stored? As the first element, it is stored at the top of the class object. Because inheritance must be considered, the best position is the first.

    cout << &(d._vptr) << endl;
    cout << &d << endl;//地址相同
  1. Virtual function table
    So how to access the virtual function table through the virtual table pointer? This is actually very simple, but it is easy to complicate.
#include <iostream>
#include <typeinfo>
using namespace std;

class Base {
    
    
   private:
   public:
    Base() {
    
    }
    ~Base() {
    
    }
    virtual void function() {
    
     std::cout << "Base::function" << std::endl; }
    virtual void function1(int num) {
    
    
        std::cout << "Base::function1 - " << num << std::endl;
    }
};

class Derived : public Base {
    
    
   public:
    void function() override {
    
     std::cout << "Derived::function" << std::endl; }
    virtual void function2() {
    
     std::cout << "Derived::function3" << std::endl; }

   private:
    virtual void function3() {
    
     std::cout << "Derived::function5" << std::endl; }
};

//一个自定义类型,这个类型用来定义一类函数,
//一类入参为空,范围值为void的函数
using func_type = void (*)();
using func_type_num = void (*)(Derived *, int);
// 相当于下面这种c语言的写法
// typedef void (*Fun)(void);

int main() {
    
    
    Derived *d2 = new Derived();

    printf("d2->_vptr:\t%x\n", d2->_vptr);           //这是虚指针
    func_type *vt_tbale = (func_type *)(d2->_vptr);  //做虚表类型转换

    for (int i = 0; i < 4; i++) {
    
    
    
        if (i == 1) {
    
    
            func_type_num f = (func_type_num)vt_tbale[i];
            f(d2, 100);
            printf("The %dst func addr\t:%x\n", i, f);
        } else {
    
    
            func_type f = vt_tbale[i];
            //这是虚函数表中第i个函数的地址
            f();
            printf("The %dst func addr\t:%x\n", i, f);
        }
    }

    return 0;
}

outputs:

d2->_vptr:	400dd0
Derived::function
The 0st func addr	:400be6
Base::function1 - 100
The 1st func addr	:400bac
Derived::function3
The 2st func addr	:400c10
Derived::function5
The 3st func addr	:400c3a

First, we regard the virtual function table as a function array, then func_type *vt_tbale = (func_type *)(d2->_vptr); Convert the virtual function pointer type to facilitate our subsequent operations. Later, you can access the inherited function through the subscript. In fact, functions inherit access permissions.
In addition, func_type_num is a type of function with parameters. The first bit must be Derived *. In fact, the member function of the class is written as a general function, and the first parameter is also the type of pointer, which is actually the type of this pointer . Of course if it is

virtual void function1(int num) const{
    
    } 
这种就要改写成 
using func_type_num = void (*)(const Derived *, int);

So a question arises, can private virtual functions be accessed through virtual tables? After we set function3 as private, the answer is yes. Therefore, virtual functions are private and very insecure! ! !

  1. Revisit the problem of multiple inheritance
  • If this base class is the first base class in the inheritance order, then the inherited virtual functions are managed by _vptr.
  • The virtual functions of other base classes are managed through the inherited d.Base2::_vptr.
  • If a virtual function of a base class is overloaded in a derived class, then this virtual function is managed by _vptr.
    It has been verified, and everyone should pay special attention to the third article.
  1. Summary
    In addition, there are many ways to write the following online blogs. When I first read it, I was really confused. The C-style pointer conversion was very confused. Until I got rid of this kind of thinking, Teacher Hou Jie was really a god. Although he didn't elaborate on this knowledge, the ppt chart when he talked about vptr gave me a deep impression.
{
    
    
    typedef void (*Fun)(void);
    Base b;
    Fun pFun = NULL;
    cout << "虚函数表地址:" << (int *)(&b) << endl;
    cout << "虚函数表 — 第一个函数地址:" << (int *)*(int *)(&b) << endl;
    // Invoke the first virtual function
    pFun = (Fun) * ((int *)*(int *)(&b));
    pFun();
    (Fun) * ((int *)*(int *)(&b) + 0);  // Base::f()
    (Fun) * ((int *)*(int *)(&b) + 1);  // Base::g()
    (Fun) * ((int *)*(int *)(&b) + 2);  // Base::h()
}

The title is indeed written as the title party, but it is indeed a lot of pitfalls before I wrote these simple examples.
If there is an error in the middle, please correct me.

The last three misunderstandings are emphasized again

  • Do not call virtual functions in the constructor of the class, this is a dangerous action
  • All objects of the same type share the virtual function table
  • Virtual functions are private and very insecure

Guess you like

Origin blog.csdn.net/niu91/article/details/109697261