Reputation: 22916
#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
Reputation: 208353
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
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
Reputation: 88711
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
Reputation: 2778
In the general compiler implementations, the virtual table of Y will have the same entries as that of Z
Upvotes: 2