3

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 :)

Sherlock
  • 7,525
  • 6
  • 38
  • 79
mutiju
  • 55
  • 5
  • In the second example, with `B`, is `buffer` still declared with `T=A`? – fredrik Jan 23 '15 at 13:21
  • view both runs independently . a buffer is initialized as follows: T* buffer = (T*) new char[size * sizeof(T)]; where T is either A or B. – mutiju Jan 23 '15 at 13:25
  • That's not exactly what I asked, in the second run - how is buffer declared. – fredrik Jan 23 '15 at 13:25
  • Nice first question! – OJFord Jan 23 '15 at 14:10
  • If buffer is an array of multiple objects of different types, then it needs to be T ** buffer = new T* [size], and initialize each to nullptr, where T is a base class to all types that can go in there (A and B). Something like std::vector> would probably be better. – Kenny Ostrom Jan 23 '15 at 14:30

3 Answers3

2

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.

Community
  • 1
  • 1
WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • Ok so either way i should try to make sure i do a placement-new when creating non trivial objects for my pool. – mutiju Jan 23 '15 at 14:00
  • Yes, Use placement new for non-POD (plain-old-data) types (and to be honest, it would do no real harm to do it for those either). – WhozCraig Jan 23 '15 at 14:02
  • i think i always assumed, that the assignment copied all data not only the visible data. a memcpy / memmove actually would work instead of just using the default copy constructor / assignment operator. – mutiju Jan 23 '15 at 14:09
  • @mutiju yeah, don't play in the C++ pool with C toys. Generally a bad idea. Just imagine what would happen with non-trivial members (ex: a `std::string`) with something like a `memcpy`. Egads. Btw. pretty good for a first question. Upticked. – WhozCraig Jan 23 '15 at 14:13
1
new char[...]

This does not construct object T (does not calls constructor). Virtual table is created during construction.

  • Yes i know, i actually wrote that im my question. I also wrote you can use new (buffer[0]) T(); in order to invoke the constructor after creating the buffers. But my question still remains, what does get copied during assignment, if it is all data the vtable pointers should be copied from the original no ? – mutiju Jan 23 '15 at 13:36
  • 1
    Not using the type system is the root of your problem. – Kenny Ostrom Jan 23 '15 at 14:11
0

The problem is not specially with virtual functions but more generally with inheritance. As buffer is an array of A, when you write :

B b;
buffer[0] = b;

you first construct a B object (first line), and later construct an A object using its copy constructor initialized with b (second line).

So when you later call buffer[0].virtual_function() you actually apply the virtual function to an A object, not to aB one.

By the way, a direct call to b.virtual_function() should still correctly call the B version since it is applied to a real B object :

B b;
buffer[0] = b;
b.virtual_function(); // calls B version

If you do not need to take a copy of the object, you could use an array of pointers.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252