What Is the Vtt for a Class

virtual table and _vptr storage scheme

The first point to keep in mind is a disclaimer: none of this is actually guaranteed by the standard. The standard says what the code needs to look like and how it should work, but doesn't actually specify exactly how the compiler needs to make that happen.

That said, essentially all C++ compilers work quite similarly in this respect.

So, let's start with non-virtual functions. They come in two classes: static and non-static.

The simpler of the two are static member functions. A static member function is almost like a global function that's a friend of the class, except that it also needs the class`s name as a prefix to the function name.

Non-static member functions are a little more complex. They're still normal functions that are called directly--but they're passed a hidden pointer to the instance of the object on which they were called. Inside the function, you can use the keyword this to refer to that instance data. So, when you call something like a.func(b);, the code that's generated is pretty similar to code you'd get for func(a, b);

Now let's consider virtual functions. Here's where we get into vtables and vtable pointers. We have enough indirection going on that it's probably best to draw some diagrams to see how it's all laid out. Here's pretty much the simplest case: one instance of one class with two virtual functions:

Sample Image

So, the object contains its data and a pointer to the vtable. The vtable contains a pointer to each virtual function defined by that class. It may not be immediately apparent, however, why we need so much indirection. To understand that, let's look at the next (ever so slightly) more complex case: two instances of that class:

Sample Image

Note how each instance of the class has its own data, but they both share the same vtable and the same code--and if we had more instances, they'd still all share the one vtable among all the instances of the same class.

Now, let's consider derivation/inheritance. As an example, let's rename our existing class to "Base", and add a derived class. Since I'm feeling imaginative, I'll name it "Derived". As above, the base class defines two virtual functions. The derived class overrides one (but not the other) of those:

Sample Image

Of course, we can combine the two, having multiple instances of each of the base and/or derived class:

Sample Image

Now let's delve into that in a little more detail. The interesting thing about derivation is that we can pass a pointer/reference to an object of the derived class to a function written to receive a pointer/reference to the base class, and it still works--but if you invoke a virtual function, you get the version for the actual class, not the base class. So, how does that work? How can we treat an instance of the derived class as if it were an instance of the base class, and still have it work? To do it, each derived object has a "base class subobject". For example, lets consider code like this:

struct simple_base { 
int a;
};

struct simple_derived : public simple_base {
int b;
};

In this case, when you create an instance of simple_derived, you get an object containing two ints: a and b. The a (base class part) is at the beginning of the object in memory, and the b (derived class part) follows that. So, if you pass the address of the object to a function expecting an instance of the base class, it uses on the part(s) that exist in the base class, which the compiler places at the same offsets in the object as they'd be in an object of the base class, so the function can manipulate them without even knowing that it's dealing with an object of the derived class. Likewise, if you invoke a virtual function all it needs to know is the location of the vtable pointer. As far as it cares, something like Base::func1 basically just means it follows the vtable pointer, then uses a pointer to a function at some specified offset from there (e.g., the fourth function pointer).

At least for now, I'm going to ignore multiple inheritance. It adds quite a bit of complexity to the picture (especially when virtual inheritance gets involved) and you haven't mentioned it at all, so I doubt you really care.

As to accessing any of this, or using in any way other than simply calling virtual functions: you may be able to come up with something for a specific compiler--but don't expect it to be portable at all. Although things like debuggers often need to look at such stuff, the code involved tends to be quite fragile and compiler-specific.

What is the price of virtual inheritance?

What is the price of virtual inheritance in terms of the time needed to access the members of the most derived class?

one offset-lookup and an add (2 instructions and a memory fetch)

In particular, if there is a non-zero price, does it pertain only to the members that are inherited through more than one path or to other members as well?

Yes, and even then not always. If the compiler has enough information to prove that the access does not need to be via indirection, it is free to short-circuit the lookup at compile time.

It'd probably be good to clarify exact when this would be the case. – Nicol Bolas

Well said sir.

Here is an example to demonstrate this. Compile with -O2 and -S options to see the optimisation in action.

#include <memory>
#include <string>

enum class proof {
base,
derived
};

// volatile forces the compiler to actually perform reads and writes to _proof
// Without this, if the compiler can prove that there is no side-effect of not performing the write,
// it can eliminate whole chunks of our test program!

volatile proof _proof;

struct base
{
virtual void foo() const {
_proof = proof::base;
}

virtual ~base() = default;
};

struct derived : base
{
void foo() const override {
_proof = proof::derived;
}
};

// factory function
std::unique_ptr<base> make_base(const std::string&name)
{
static const std::string _derived = "derived";

// only create a derived if the specified string contains
// "derived" - on my compiler this is enough to defeat the
// optimiser

if (name == _derived) {
return std::make_unique<derived>();
}
else {
return {};
}
}

auto main() -> int
{
// here the compiler is fully aware that p is pointing at a derived
auto p = std::make_unique<derived>();

// therefore the call to foo() is made directly (in fact, clang even inlines it)
p->foo();

// even here, the compiler 'knows' that b is pointing at a 'derived'
// so the call to foo is made directly (and indeed on my compiler, completely
// inlined)
auto b = std::unique_ptr<base>(new derived);
b->foo();

// here we assign a derived to b via indirect construction through a string.
// Unless the compiler is going to track this string and follow the logic in make_base
// (and on my compiler it does not) this will prevent the virtual call to foo() from
// being turned into a direct call.
// Therefore, this call will be made via the virtual function table of *b
b = make_base("derived");
if (b) {
b->foo();
}

return 0;
}

What is the VTable's method pointers order if class implements more than 1 interface?

The order of vtable members in a class is not well-defined. Indeed, you can (and will!) find data members between vtable pointers. If you're writing COM, you should cast to whatever interface you want to return in your QueryInterface prior to writing it to the pointer you're putting the result in. That is, something like:

HRESULT QueryInterface(REFIID riid, LPVOID *ppvObject) {
if (*riid == IID_MY_INTERFACE) {
*ppvObject = static_cast<IMyInterface *>(this);
return S_OK;
} else if (*riid == IID_SOMETHING_ELSE) {
*ppvObject = static_cast<ISomethingElse *>(this);
return S_OK;
} else /* ... */
}

The compiler will take care of finding the right offset for the interface for you.

As for how it actually works, think about what this implies - each of the interfaces must exist as an object at some subrange of offsets within the object.. Say you have a class heirarchy that looks like this:

class IA { virtual void foo() = 0; int x;};
class IB { virtual void bar() = 0; int y; };
class C : public IA, public IB { int bar; };

Your in-memory layout might look like this:

00000000 vtable ptr for C
00000004 vtable ptr for Ia
00000008 int x
0000000b vtable ptr for Ib
00000010 int y
00000014 int bar

Here you can get a pointer to an Ia by getting an offset 0x00000004 into the class, or Ib at 0x0000000b.

It may in some cases by possible to optimize this further:

00000000 vtable ptr for C and Ia
00000004 int x
00000008 vtable ptr for Ib
0000000b int y
00000010 int bar

I'm not sure if the win32 C++ ABI actually does this. If you do do this, then the C vtable starts with the same members as the Ia vtable, then adds on extra ones at the end.

C++ Undefined Reference to vtable and inheritance

Why the error & how to resolve it?

You need to provide definitions for all virtual functions in class A. Only pure virtual functions are allowed to have no definitions.

i.e: In class A both the methods:

virtual ~A();
virtual void doWork();

should be defined(should have a body)

e.g.:

A.cpp

void A::doWork()
{
}
A::~A()
{
}

Caveat:
If you want your class A to act as an interface(a.k.a Abstract class in C++) then you should make the method pure virtual.

virtual void doWork() = 0;

Good Read:

What does it mean that the "virtual table" is an unresolved external?

When building C++, the linker says my constructors, destructors or virtual tables are undefined.

Undefined reference to vtable

So, I've figured out the issue and it was a combination of bad logic and not being totally familiar with the automake/autotools world. I was adding the correct files to my Makefile.am template, but I wasn't sure which step in our build process actually created the makefile itself. So, I was compiling with an old makefile that had no idea about my new files whatsoever.

Thanks for the responses and the link to the GCC FAQ. I will be sure to read that to avoid this problem occurring for a real reason.



Related Topics



Leave a reply



Submit