virtual inheritance downcast

G

Guest

#include <iostream>
using namespace std;

class Base
{
public:
Base() {}
~Base() {}
};

class Derived : virtual public Base
{
public:
Derived() {}
~Derived() {}
};

int main()
{
Base *b = new Derived();
Derived *d = (Derived*)b; //line 22. The problem line!

return 0;
}

For the code above, I got the following error message:
downcast.cc: In function `int main()':
downcast.cc:22: error: cannot convert from base `Base' to derived type `
Derived' via virtual base `Base'

That means it is illegal to explicit convert a VIRTUAL base point to the
derived point. (non-virtual is ok) Can somebody explain it? ( The coding
style is bad, please just ignore it. ^_^)

Thanks in advance!
 
T

Tom Widmer

#include <iostream>
using namespace std;

class Base
{
public:
Base() {}
~Base() {}
};

class Derived : virtual public Base
{
public:
Derived() {}
~Derived() {}
};

int main()
{
Base *b = new Derived();
Derived *d = (Derived*)b; //line 22. The problem line!

return 0;
}

For the code above, I got the following error message:
downcast.cc: In function `int main()':
downcast.cc:22: error: cannot convert from base `Base' to derived type `
Derived' via virtual base `Base'

That means it is illegal to explicit convert a VIRTUAL base point to the
derived point. (non-virtual is ok) Can somebody explain it? ( The coding
style is bad, please just ignore it. ^_^)

The compiler is correct - the conversion is illegal. Equivalently (and
more specifically), you cannot downcast a virtual base class pointer to
a derived class pointer using static_cast. This is because the case may
involve some pointer adjustment that depends on the dynamic type of the
object pointed to. e.g.

class Base
{
char i;
};

class Derived : virtual public Base
{
int j
};

class Derived2 : virtual public Base
{
int k;
};

class DiamondBottom: public Derived, public Derived2
{
int l;
};

int main()
{
Base *b = new DiamondBottom();
Derived *d = static_b; //line 22. The problem line!

return 0;
}

Now, in a DiamondBottom object, the Derived2 subobject is likely to be
placed after the Derived subobject, which means that the pointer
adjustment required for a conversion from a Base* to a Derived2* depends
on whether the Derived2 is part of a larger DiamondBottom object or not.

E.g. a Derived2 object looks like this (vtable pointers left out):
i
k

while a DiamondBotton object might look like this (vtable pointers left
out):
i
j
k
l

so that i and k are in different relative positions depending on the
dynamic type of the object.

The solution is to use dynamic_cast, which performs the correct
conversion by checking the real type of the object.

Tom
 
A

Alf P. Steinbach

* (e-mail address removed):
#include <iostream>
using namespace std;

class Base
{
public:
Base() {}
~Base() {}
};

class Derived : virtual public Base
{
public:
Derived() {}
~Derived() {}
};

int main()
{
Base *b = new Derived();
Derived *d = (Derived*)b; //line 22. The problem line!

Yes, don't use C-style casts. A C-style cast tells the compiler that
your Really Know What You're Doing. Which you absolutely don't, here.

return 0;
}

For the code above, I got the following error message:
downcast.cc: In function `int main()':
downcast.cc:22: error: cannot convert from base `Base' to derived type `
Derived' via virtual base `Base'

That means it is illegal to explicit convert a VIRTUAL base point to the
derived point. (non-virtual is ok) Can somebody explain it? ( The coding
style is bad, please just ignore it. ^_^)

Due to the virtual inheritance the Base sub-object can be a shared Base
sub-object for any number of Derived objects. So which one should the
cast produce a pointer to? There's no way to tell, strictly locally,
from the static type information, and for consistency the rules don't
distinguis situations like above where the compiler /could/ have figured
it out by looking at the rest of the program text.

So to do this you need access to run time type information, what the
object really is when the cast is attempted, and you need to allow for
failure.

And to do that, use a dynamic_cast (which requires at least one virtual
member function in Base, e.g. the destructor).
 
F

Fei Liu

#include <iostream>
using namespace std;

class Base
{
public:
Base() {}
~Base() {} virtual ~Base() {}
};

class Derived : virtual public Base
{
public:
Derived() {}
~Derived() {}
};

int main()
{
Base *b = new Derived();
Derived *d = (Derived*)b; //line 22. The problem line!
Derived *d = dynamic_cast<Derived *>(b);

if you don't have virtual members in base class, then you can't use
dynamic_cast. In that case, reinterpret_cast is the only option but it will
cause undefined behavior on some platform...
 
D

dan2online

Fei said:
dynamic_cast. In that case, reinterpret_cast is the only option but it will
cause undefined behavior on some platform...

The reinterpret_cast creates a value of a new type that has the SAME
BIT pattern as its argument so it will cause undefined behavior in ANY
platform, not some platforms from the base class to the derived class.

FYI, here is a test case:
======================
#include <iostream>
using namespace std;

class Base
{
public:
float base_v;
Base(): base_v(999) { }
~Base() {}

};

class Derived : virtual public Base
{
public:
int derived_v;
Derived(): derived_v(0) {}
~Derived() {}

};

int main()
{
Base *b = new Derived();
Derived *d = reinterpret_cast<Derived *>(b); //line 22. The
problem line!

cout << "b->base_v=" << b->base_v << endl;

//d->derived_v shows a weired result
cout << "d->derived_v=" << d->derived_v << endl;

// the following will throw segamentation fault!
cout << "d->base_v=" << d->base_v << endl;

return 0;
}
==============================
 
P

Phlip

dan2online said:
The reinterpret_cast creates a value of a new type that has the SAME
BIT pattern as its argument so it will cause undefined behavior in ANY
platform, not some platforms from the base class to the derived class.
....

class Derived : virtual public Base

Nice sample, but if you take the 'virtual' out, then on some platforms the
address of the Derived will be the same as the address of the Base within
it. You can assert for that.

In that situation, this rule takes effect: "reinterpret_cast works correctly
if it casts a pointer or reference to a type that an address truly
contains".

(Someone will doubtless look the correct verbiage up for me: My copy of the
old Standard is on my old hard drives.)

So in that narrow situation the behavior is defined.
 
D

dan2online

Phlip said:
dan2online wrote:

Nice sample, but if you take the 'virtual' out, then on some platforms the
address of the Derived will be the same as the address of the Base within
it. You can assert for that.

If the "virtual" disappears, the original example has no problem then.
 
M

Michiel.Salters

Phlip said:
Nice sample, but if you take the 'virtual' out, then on some platforms the
address of the Derived will be the same as the address of the Base within
it. You can assert for that.

If you leave the virtual in, it might still be the case. Of course, you
can't
compare a Derived* and a Base*; so for your assert you'd still need to
cast the other side of the comparison as well (or the first side back,
depending on how you write the assert). So the assert may cause
undefined behavior; it's a bit tricky to say without code.

Still, IIRC it's not the creation of the Derived* that will trip you,
but the actual
use as if it indeed points to a Derived*. You should be able to cast it
back to
the original Base*. Not very useful, I admit.

This doesn't matter a lot when it comes to Undefined Behavior and
platforms.
Undefined Behavior is what the ISO standard says about a piece of code.

If it's UB according to the standard, it's UB for all compilers, and if
not, for none.
(Disregarding trivial code like const int i = 4/sizeof(int)-4. A
division by zero is
still UB everywhere)

HTH,
Michiel Salters
 
R

Ron Natalie

Fei said:
if you don't have virtual members in base class, then you can't use
dynamic_cast. In that case, reinterpret_cast is the only option but it will
cause undefined behavior on some platform...
Actually the C-style cast here will do a static_cast. This isn't
undefined as long as you know that the dynamic type really is
the type being cast to.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top