Search code examples
c++constructorvirtualvariable-assignmentvtable

Initialisation of objects with/without vtable


Say I have a pool that allocates some buffer.

int size = 10;

T* buffer = (T*) new char[size * sizeof(T)];

If I now want to assign some data to the buffer, i do the following.

buffer[0] = data;

My question is now what is the difference in initialization of objects that have vtable and those that don't.

From what I can see, I can without a problem assign classes to this buffer, and as long as I don't call any virtual functions, function calls work just fine. e.g.

class A{
    void function(){}
};

A a;
buffer[0] = a;
a.function(); // works

But:

class B{
    void function(){}
    virtual void virtual_function(){}
};

B b;
buffer[0] = b;
b.function(); // does work
b.virtual_function() // does not work.

Why does non-virtual function work?

Is it because the function is statically declared due to it being a normal class function and therefore is being copied when we do the assignment?

But then it doesn't make sense that I need to call the constructor on the buffer I created in case I need to make sure the virtual function works as well. new (buffer[0]) T(); in order to call the constructor on the object created.

Both examples first create the appropriate size of the buffer then do a assignment, view this as a pool where I pre-allocate memory depending on the amount of objects I want to fit in the pool.

Maybe I just looked at this to long and confused my self :)


Solution

  • Your non-virtual functions "work" (a relative term) because they need no vtable lookup. Under the hood is implementation-dependent, but consider what is needed to execute a non-virtual member.

    You need a function pointer, and a this. The latter is obvious, but where does the fn-ptr come from? its just a plain function call (expecting a this, then any supplied arguments). There is no polymorphic potential here. No vtable lookup required means the compiler can (and often does) simply take the address of what we think is an object, push it, push any supplied args, and invoke the member function as a plain-old-call. The compiler knows which function to call, and needs no vtable-intermediary.

    It is not uncommon for this to cause headaches when invoking non-static, non-virtual member function on illicit pointers. If the function is virtual, you'll generally (if you're fortunate) blow up on the call. If the function is non-virtual, you'll generally (if you're fortunate) blow up somewhere in the body of the function as it tries to access member data that isn't there (including a vtable-directed execution if your non-virtual calls a virtual).

    To demonstrate this, consider this (obviously UB) example. Try it.

    #include <iostream>
    
    class NullClass
    {
    public:
        void call_me()
        {
            std::cout << static_cast<void*>(this) << '\n';
            std::cout << "How did I get *here* ???" << '\n';
        }
    };
    
    int main()
    {
        NullClass *noObject = NULL;
        noObject->call_me();
    }
    

    Output (OSX 10.10.1 x64, clang 3.5)

    0x0
    How did I get *here* ???
    

    The bottom line is no vtable is bound to the object when you allocate raw memory and assign a pointer via a cast as you are. If you want to do this, you need to construct the object via placement-new. And in so doing, do not forget you must also destroy the object (which has nothing to do with the memory it occupies, as you're managing that separately) by calling its destructor manually.

    Finally, the assignment you're invoking does not copy the vtable. Frankly there is no reason to. The vtable of a properly constructed object is already properly built, and referenced by the vtable pointer for a given object instance. Said-pointer does not participate in object copying, which has its own set of mandated requirements from the language standard.