C++: is a class with virtual base but without virtual functions polymorphic and has VTable?
Asked Answered
S

3

7

Consider the following code:

#include <iostream>
#include <typeinfo>
#include <type_traits>

using namespace std;

struct A { int data; };
struct B1 : A {};
struct B2 : virtual A {};

struct Base1 : virtual A {};
struct Base2 : virtual A {};
struct Derived : Base1, Base2 {};

int main() {
  cout << sizeof(B1) << endl;
  cout << sizeof(B2) << endl;
  cout << sizeof(Derived) << endl;

  cout << std::is_polymorphic<B1>::value << endl;
  cout << std::is_polymorphic<B2>::value << endl;
  cout << std::is_polymorphic<Derived>::value << endl;
  return 0;
}

On my system it prints

4
8
12
0
0
0

Which means that none of these classes is polymorphic. However, sizes of B1 and B2 differ by exactly size of a pointer, which is probably a pointer to vtable. I've ran gcc with -fdump-class-hierarchy and got:

Vtable for B2
B2::_ZTV2B2: 3u entries
0     4u
4     (int (*)(...))0
8     (int (*)(...))(& _ZTI2B2)

VTT for B2
B2::_ZTT2B2: 1u entries
0     ((& B2::_ZTV2B2) + 12u)

Class B2
   size=8 align=4
   base size=4 base align=4
B2 (0x0x3ca5400) 0 nearly-empty
    vptridx=0u vptr=((& B2::_ZTV2B2) + 12u)
  A (0x0x3c972d8) 4 virtual
      vbaseoffset=-12

Here is a question: what is a polymorphic class? Does 'having vtable' means 'be polymorphic and have RTTI' and vice-versa? If not, why do it have to have at least one virtual function to be polymorphic, if it's going to have vtable anyway?

Looks like definition of polymorphic class differs from 'the class that has a vtable', because, as we can see above, B2 is not a polymorphic, but has a vtable and, as I think, should have a RTTI. In this document it's clearly stated that "polymorphic - i.e. have at least one virtual function. (This is necessary to allow the generated dispatch code to use RTTI on the classes.)".

Serafinaserafine answered 24/11, 2013 at 12:20 Comment(1)
I mean, what's the goal of separating 'polymorphic class' and 'class that has a vtable'?Serafinaserafine
T
6

You are missing a detail here: virtual tables are an implementation detail. As a result:

  • the Standard defines polymorphic classes as classes that can be used in a polymorphic sense: ie, where behavior can be overriden
  • compilers use virtual tables to implement some features/behaviors mandated by the Standard, which happen to include both polymorphic classes AND virtual bases.

Thus, yes the compilers I know (MSVC and those following the Itanium ABI such as gcc, icc and Clang) will use virtual tables to provide the RTTI necessary for dynamic_cast to work in the presence of virtual bases... and no this has nothing to do really with whether a class is polymorphic or not.

On a tangent, though, Prefer Composition Over Inheritance implies that there is little reason to inherit from a class if there is no behavior that can be overriden.

Timorous answered 24/11, 2013 at 12:37 Comment(3)
So, virtual bases incidentally let dynamic_cast work on certain compilers? That seems like undefined behaviour. The Standard idea of polymorphism - what makes dynamic_cast well formed - only refers to having at least one virtual function, not virtual base, so compilers doing that, & users relying on it, seem quite irresponsible.Gladysglagolitic
@underscore_d: I... interesting. I'd have to check if the Standard made a special provision for this case but at least there's no such thing on the cppreference page so I'm not hopeful.Timorous
To be clearer, I was only thinking of downcasting with dynamic_cast. Other cases can work without RTTI - but then, in such cases so can a more appropriate static_cast or even just an implicit conversion.Gladysglagolitic
A
3

Here is a defenition of std::is_polymorphic

If T is a polymorphic class (that is, a class that declares or inherits at least one virtual function), provides the member constant value equal true. For any other type, value is false.

Since no functions are defined virtual, it will return false.

To elaborate, we should distinguish between polymorphism and vtables. In C++ vtables are indeed needed for polymorphism, but also for other non-polymorphic concepts too.

I could try to explain virtual inheritance, which when used with multiple inheritance will create a vtable, but this link does it better.

Alleviate answered 24/11, 2013 at 12:24 Comment(2)
Yep, it's ok from standard's POV. But what was the reason behind this solution? Why don't make 'polymorphic class' and 'class that has a vtable' same things?Serafinaserafine
@Serafinaserafine I'll elaborate in my answer, but basically just because it has a virtual table, it doesnt mean that its polymorphic.Alleviate
H
2

For those curious about the effects of the VTable provided by the inheritance.

The following sample

struct VB{
   // virtual int f(){}
};

struct IA:virtual VB{};

int main(int argc, char** argv) {
    VB* o = new IA;

    IA* iap = dynamic_cast<IA*>(o);

}

will not compile (g++ 4.8):

main.cpp: In function ‘int main(int, char**)’: main.cpp:26:34: error: cannot dynamic_cast ‘o’ (of type ‘struct VB*’) to type ‘struct IA*’ (source type is not polymorphic) IA* iap = dynamic_cast(o);

While uncommenting the int f() member gives the desired result.

Heredity answered 24/11, 2013 at 12:45 Comment(2)
that may be compiler's bugOakley
@BЈовић No, it is the expected behaviour according to specs.Heredity

© 2022 - 2024 — McMap. All rights reserved.