4

Consider an inheritance hierarchy like this: A / \ B1 B2 \ / C | D Realized in C++ like so:

class A {
public:
    A() {};
    virtual ~A() = 0;
    double a;
};

A::~A() {};

class B1 : virtual public A {
public:
    B1() {}
    virtual ~B1() {}
    double b1;
};

class B2 : virtual public A {
public:
    B2() {}
    virtual ~B2() {}
    double b2;
};

class C : public B1, public B2 {
public:
    C() {}
    virtual ~C() {}
    double c;
};

class D : public C {
public:
    D() {}
    virtual ~D() {}
    double d;
};

Now, obviously I can do something like this:

D *d = new D();
A *a = (A*) d;
D *d_down = dynamic_cast<D*>(a);
assert(d_down != NULL); //holds

However, I can't seem to figure out how to get same behavior using arrays. Please consider the following code sample to see what I mean by that:

D *d[10];
for (unsigned int i = 0; i < 10; i++) {
    d[i] = new D();
}

A **a = (A**) d;
D *d_down = dynamic_cast<D*>(a[0]);
assert(d_down != NULL); //fails!

So my questions would be:

  • Why does to above assertion fail?
  • How can I achieve the desired behavior?
  • I noticed, by chance, that the dynamic_cast above works if I remove the double fields from classes A through D. Why is that?
15
  • 2
    Is not that the Diamond-Problem? Commented Aug 24, 2016 at 12:48
  • 5
    @FirstStep Resolved by virtual. Commented Aug 24, 2016 at 12:51
  • 5
    In the array example the type of d is D*[10]. This will decay to a D**. Unfortunately this is not the same as A**. Instead you have to create another array, A* a[10] and copy the pointer from d into a (which will be much easier if you used std::vector instead). Now you can use a the way you expect. Commented Aug 24, 2016 at 12:51
  • 1
    A *a = (A*) d; is trivial (no need of cast) & A **a = (A**) d; is dangerous! Commented Aug 24, 2016 at 12:51
  • 1
    That's a very dangerous cast. Consider a[0] = new C;. Now your array of D* contains something that's not a D*. Commented Aug 24, 2016 at 13:09

1 Answer 1

13

The problem is, that (A*)d is not numerically equal to d!

See, you have an object like

+---------------------+
| A: vtable ptr A     | <----- (A*)d points here!
|    double a         |
+---------------------+
+---------------------+
| D:                  | <----- d points here (and so do (C*)d and (B1*)d)!
|+-------------------+|
|| C:                ||
||+-----------------+||
||| B1: vptr B1,C,D |||
|||     double b1   |||
||+-----------------+||
||+-----------------+|| <----- (B2*)d points here!
||| B2: vptr B2     |||
|||     double b2   |||
||+-----------------+||
||    double c       ||
|+-------------------+|
|    double d         |
+---------------------+

When you cast a D* to A*, via static_cast or dynamic_cast, the compiler will inject the necessary arithmetic for you.

But when you cast it via reinterpret_cast, or cast a D** to A**, which is the same thing, the pointer will keep its numeric value, because the cast does not give the compiler the right to dereference the first layer to adjust the second layer.

But then the pointer will still point at D's vtable, not A's vtable, and therefore won't be recognized as A.


Update: I checked the layout in compiler (g++) and the picture should now reflect the actual layout generated in the case in question. It shows that virtual bases live at negative offsets. This is because a virtual base is at different offset depending on the actual type, so it can't be part of the object itself.

The address of object does coincide with address of the first non-virtual base. However, the specification does not guarantee it for objects with virtual methods or bases, so don't rely on it either.


This shows the importance of using appropriate casts. Conversions that can be done implicitly, via static_cast, dynamic_cast or function-style cast are reliable and compiler will inject appropriate adjustments.

However using reinterpret_cast clearly indicates the compiler will not adjust and you are on your own.

A *a = static_cast<A *>(d);

is ok, but

A **aa = static_cast<A **>(&d);

is a compilation error.

The problem with C-style cast is that it does a static_cast when possible and reinterpret_cast otherwise, so you can cross the border to the undefined behavior land without noticing. That's why you shouldn't use C-style cast in C++. Ever.

Note that due to aliasing rules, writing reinterpret_cast essentially always implies Undefined Behavior. And at least GCC does optimize based on aliasing rules. The only exception is cv-(signed/unsigned) char *, which is exempt from strict aliasing. But it only ever makes sense to cast to and from pointers to standard layout types, because you can't rely on layout of objects with bases (any, not just virtual) and/or virtual members.

Sign up to request clarification or add additional context in comments.

4 Comments

This is a great answer. However, since you bolded the thing about C-style casts: there is actually one valid use case for them, see here
@M.M, I am not sure the case should be called valid. It is something that is permitted with C-style casts, but not the other kinds, but it is still breaking the rules, though only rules given by library author, not language standard.
what library author?
@M.M, any whose private you are busting.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.