Search code examples
c++inheritanceabstract-classvirtual

How does a virtual table handle pure virtual functions


How does the compiler implement a pure virtual function? How is the vtable of a class having pure virtual functions implemented? Why do we always need to override pure virtual functions? What happens to the vtable of a derived class when we override a pure virtual function?


Solution

  • The c++ standard doesn't specify the implementation of virtual methods.

    Generally it's implemented as something like an array of function pointers, pure virtual functions are often pointers to some special function which throws an error.

    You have to override pure virtual functions as otherwise when something tries to call those functions what would happen? If you don't want to have to override a particular function don't make it pure virtual in the base class.

    For example you could emulate virtual functions with code like this:

    #include <iostream>
    #include <string>
    #include <vector>
    
    class A
    {
    public:
        A() : vtable(2)
        {
            vtable[0] = &A::aimpl;
            // B is pure virtual
            vtable[1] = &A::pureVirtualFunction;
        }
    
        void a()
        {
            ((*this).*(vtable[0]))();
        }
    
        void b()
        {
            ((*this).*(vtable[1]))();
        }
    
    protected:
        std::vector<void (A::*)()> vtable;
    
    private:
        void aimpl()
        {
            std::cout << "A::a\n";
        }
    
        void pureVirtualFunction()
        {
            throw std::runtime_error("function is pure virtual"); 
        }
    };
    
    class B : public A
    {
    public:
        B()
        {
            // Note: undefined behaviour!!! Don't do this in real code
            vtable[1] = reinterpret_cast<void (A::*)()>(&B::bimpl);
        }
    
    private:
        void bimpl()
        {
            std::cout << "B::b\n";
        }
    };
    
    int main()
    {
        A a;
        a.a();
        try
        {
            a.b();
        }
        catch (std::exception& ex)
        {
            std::cout << ex.what() << "\n";
        }
        B b;
        b.a();
        b.b();
        return 0;
    }
    

    The real implementation is more complex with derived classes being able to add to the vtable, merging vtables from multiple inheritance etc.