Aquarius_Girl
Aquarius_Girl

Reputation: 22916

Can an empty virtual table exist?

#include <iostream>
using namespace std;

class Z
{
public:
    int a;
    virtual void x () {}
};

class Y : public Z
{
public:
    int a;
};

int main() 
{
    cout << "\nZ: "  << sizeof (Z);
    cout << "\nY: "  << sizeof (Y);
} 

Because Y inherits Z, so it will also have a virtual table. Fine. But, it doesn't have any virtual functions, so what will be the contents of the virtual table of Y?
Will it be empty?

Upvotes: 3

Views: 749

Answers (4)

The gory details have already been added in other answers, but from a very high level point of view you have to think that the derived type Y does have all inherited virtual functions from Z, it just does not provide overrides for any of them (well, for the single one).

The whole virtual function table idea is that all types that derive from that base will have a compatible table. When the compiler needs to find the particular implementation of the virtual method to call, it knows that it can depend on the table being present, and in the element of the table being a pointer to the final-overrider of that method for this particular object even if that final-overrider happens to be the first and only overrider as per your example.

Upvotes: 1

NPE
NPE

Reputation: 500367

This is entirely compiler-dependent. When I force instantiation of Y and Z, g++ 4.4.5 produces two distinct virtual tables for Y and Z that have the same size.

Both tables point to the same x() but point to different typeinfo structures:

;=== Z's virtual table ===
_ZTV1Z:
        .quad   0
        .quad   _ZTI1Z     ; Z's type info
        .quad   _ZN1Z5xEv  ; x()

_ZTI1Z:
        ; Z's type info (omitted for brevity)

;=== Y's virtual table ===
_ZTV1Y:
        .quad   0
        .quad   _ZTI1Y     ; Y's type info
        .quad   _ZN1Z5xEv  ; x()

_ZTI1Y:
        ; Y's type info (omitted for brevity)

Upvotes: 5

In the example you posted GCC will by default optimise away the vtable entirely. Because it's only one translation unit and everything is visible to it this is possible.

I changed your example to:

#include <iostream>
using namespace std;

class Z
{
public:
    int a;
    virtual void x () const {}
};

class Y : public Z
{
public:
    int a;
};

int main()
{
    Y y;
    const Z& z1=y;
    const Z& z2=Z();
    z1.x(),z2.x();
    cout << "\nZ: "  << sizeof (Z);
    cout << "\nY: "  << sizeof (Y);
}

In this case a vtable is being generated in the output:

nm a.out|c++filt|grep -i vtable
08048880 V vtable for Y
08048890 V vtable for Z
0804a040 V vtable for __cxxabiv1::__class_type_info@@CXXABI_1.3
0804a120 V vtable for __cxxabiv1::__si_class_type_info@@CXXABI_1.3

If we generate the assembly with -S then we can find the constructors (mangled as _ZN1ZC2Ev and _ZN1YC2Ev respectively on my system). These take care of setting up the vtables (_ZTV1Z and _ZTV1Y):

Constructor for Z:

_ZN1ZC2Ev:
.LFB970:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        movl    8(%ebp), %eax
        movl    $_ZTV1Z+8, (%eax)
        popl    %ebp
        .cfi_def_cfa 4, 4
        .cfi_restore 5
        ret

And Y:

_ZN1YC2Ev:
.LFB972:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        subl    $24, %esp
        movl    8(%ebp), %eax
        movl    %eax, (%esp)
        call    _ZN1ZC2Ev
        movl    8(%ebp), %eax
        movl    $_ZTV1Y+8, (%eax)
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret

The interesting thing here is that what gets put in the vtable in both constructors is essentially the same.

Upvotes: 4

mukeshkumar
mukeshkumar

Reputation: 2778

In the general compiler implementations, the virtual table of Y will have the same entries as that of Z

Upvotes: 2

Related Questions