Click here to Skip to main content
15,886,199 members
Articles / Programming Languages / C++
Tip/Trick

Class Mechanics

Rate me:
Please Sign up or sign in to vote.
4.00/5 (2 votes)
15 Mar 2019MIT2 min read 3.2K   1   1
A post about several topics dealing with classes and what happens under the hood when we use member functions, inheritance, and virtual functions

A friend asked me to make a post about the mechanics of virtual functions in C++. I thought about it for a few days and decided to write a broader post about several topics dealing with classes and what happens under the hood when we use member functions, inheritance, and virtual functions.

Member Functions

You can think of member functions as no different than static members, or functions defined outside the class, except the compiler adds a hidden first parameter this. That’s how a member function is able to operate on an instance of a class. So when you write this:

C++
class C
{
public:
    void MemFun(int arg) {}
};

C c;
c.MemFun(1);

What you are really getting is this:

C++
class C {};
void C_MemFun(C* _this_, int arg) {}

C c;
C_MemFun(&c, 1);

A compiler generated C_MemFun with extra parameter of type C*.

Inheritance

Here, I want to briefly mention the order in which constructors and destructors are called: if D derives from B and you create an instance of D, the constructors will be executed in order: Bs first, Ds second. So when the control enters Ds constructor, B part of the class has been initialized already. The opposite is true for destructors. When you delete an instance of D, the destructors will be executed in order: Ds first, Bs second. The code at the end of this post will demonstrate it. BTW, I’m skipping over virtual and pure virtual destructors in this post since that’s a topic that could easily become its own blog post. 😉

Virtual Functions

Several things happen when we declare a virtual function: for each class, the compiler creates a hidden table called virtual function table. Pointers to virtual functions are stored in this table. Next, each instance of a class gets a hidden member variable called virtual function table pointer that, as the name implies, points at the virtual function table for that particular class. So an instance of B has a hidden member pointing at B‘s virtual function table, and an instance of D… ditto. When you create virtual functions, the compiler generates the following dispatch code:

C++
template<typename T, typename... A>
void VirtualDispatch(T* _this_, int VirtualFuncNum, A&&... args)
{
    _this_->VirtualTablePtr[VirtualFuncNum](_this_, forward<A>(args)...);
}

For an instance of type T called _this_, it does a lookup in the VirtualTablePtr for virtual function number VirtualFuncNum, and calls it with _this_as the first argument plus whatever extra parameters it accepts. Depending on the type of _this_, VirtualTablePtr will point at a different virtual function table and that’s more or less how we get runtime polymorphism in C++. 🙂

Complete Listing

C++
#include <iostream>
#include <utility>
#include <cstdlib>
using namespace std;

#define VIRTUAL_FUNCTION_1 0
#define VIRTUAL_FUNCTION_2 1

typedef void(*VirtualFunctionPtr)(void*, int arg);

struct Base
{
    static void BaseConstructor(void* _this_)
    {
        ((Base*)_this_)->VirtualTablePtr = BaseVirtualTable;
        ((Base*)_this_)->Member1 = rand() % 100;
        cout << "BaseConstructor(" << _this_ <<
            ", Member1 = " << ((Base*)_this_)->Member1 << ")" << endl;
    }
    
    static void BaseDestructor(void* _this_)
    {
        cout << "BaseDestructor(" << _this_ << ")" << endl;
    }

    static void BaseVirtualFunction_1(void* _this_, int arg)
    {
        cout << "Base(Member1 = " << ((Base*)_this_)->Member1 <<
            ")::BaseVirtualFunction_1(" << arg << ")" << endl;
    }
    
    static void BaseVirtualFunction_2(void* _this_, int arg)
    {
        cout << "Base(Member1 = " << ((Base*)_this_)->Member1 <<
            ")::BaseVirtualFunction_2(" << arg << ")"  << endl;
    }

    VirtualFunctionPtr* VirtualTablePtr;
    int Member1;
    static VirtualFunctionPtr BaseVirtualTable[3];
};

VirtualFunctionPtr Base::BaseVirtualTable[3] =
{
    &Base::BaseVirtualFunction_1,
    &Base::BaseVirtualFunction_2
};

struct Derived
{
    static void DerivedConstructor(void* _this_)
    {
        Base::BaseConstructor(_this_);
        ((Derived*)_this_)->VirtualTablePtr = DerivedVirtualTable;
        ((Derived*)_this_)->Member2 = rand() % 100;
        cout << "DerivedConstructor(" << _this_ <<
            ", Member2 = " << ((Derived*)_this_)->Member2 << ")" << endl;
    }
    
    static void DerivedDestructor(void* _this_)
    {
        cout << "DerivedDestructor(" << _this_ << ")" << endl;
        Base::BaseDestructor(_this_);
    }

    static void DerivedVirtualFunction_1(void* _this_, int arg)
    {
        cout << "Derived(Member1 = " << ((Base*)_this_)->Member1 <<
            ", Member2 = " << ((Derived*)_this_)->Member2 <<
            ")::DerivedVirtualFunction_1(" << arg << ")"  << endl;
    }
    
    static void DerivedVirtualFunction_2(void* _this_, int arg)
    {
        cout << "Derived(Member1 = " << ((Base*)_this_)->Member1 <<
            ", Member2 = " << ((Derived*)_this_)->Member2 <<
            ")::DerivedVirtualFunction_2(" << arg << ")"  << endl;
    }

    VirtualFunctionPtr* VirtualTablePtr;
    int __Space_for_Base_Member1__;
    int Member2;
    static VirtualFunctionPtr DerivedVirtualTable[3];
};

VirtualFunctionPtr Derived::DerivedVirtualTable[3] =
{
    &Derived::DerivedVirtualFunction_1,
    &Derived::DerivedVirtualFunction_2
};

template<typename T, typename... A>
void VirtualDispatch(T* _this_, int VirtualFuncNum, A&&... args)
{
    _this_->VirtualTablePtr[VirtualFuncNum](_this_, forward<A>(args)...);
}

int main(int argc, char** argv)
{
    srand((unsigned int)time(NULL));

    cout << "===> Base start <===" << endl;
    Base* base = (Base*)operator new(sizeof(Base));
    Base::BaseConstructor(base);

    VirtualDispatch(base, VIRTUAL_FUNCTION_1, rand() % 100);
    VirtualDispatch(base, VIRTUAL_FUNCTION_2, rand() % 100);

    Base::BaseDestructor(base);
    operator delete(base);
    cout << "===> Base end <===" << endl << endl;

    cout << "===> Derived start <===" << endl;
    Base* derived = (Base*)operator new(sizeof(Derived));
    Derived::DerivedConstructor(derived);

    VirtualDispatch(derived, VIRTUAL_FUNCTION_1, rand() % 100);
    VirtualDispatch(derived, VIRTUAL_FUNCTION_2, rand() % 100);

    Derived::DerivedDestructor(derived);
    operator delete(derived);
    cout << "===> Derived end <===" << endl << endl;

    return 1;
}
===> Base start <===

BaseConstructor(0x1005845d0, Member1 = 29)

Base(Member1 = 29)::BaseVirtualFunction_1(59)

Base(Member1 = 29)::BaseVirtualFunction_2(52)

BaseDestructor(0x1005845d0)

===> Base end <===



===> Derived start <===

BaseConstructor(0x10060b6a0, Member1 = 52)

DerivedConstructor(0x10060b6a0, Member2 = 80)

Derived(Member1 = 52, Member2 = 80)::DerivedVirtualFunction_1(40)

Derived(Member1 = 52, Member2 = 80)::DerivedVirtualFunction_2(79)

DerivedDestructor(0x10060b6a0)

BaseDestructor(0x10060b6a0)

===> Derived end <===

Program output.
This article was originally posted at https://vorbrodt.blog/2019/02/07/class-mechanics

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generala very good review Pin
Southmountain23-Mar-19 10:51
Southmountain23-Mar-19 10:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.