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?
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Matthias
  • 506
  • 5
  • 16
  • 2
    Is not that the [Diamond-Problem](http://www.cprogramming.com/tutorial/virtual_inheritance.html)? – Khalil Khalaf Aug 24 '16 at 12:48
  • 5
    @FirstStep Resolved by `virtual`. – πάντα ῥεῖ Aug 24 '16 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. – Some programmer dude Aug 24 '16 at 12:51
  • 1
    `A *a = (A*) d;` is trivial (no need of cast) & `A **a = (A**) d;` is dangerous! – iammilind Aug 24 '16 at 12:51
  • @πάνταῥεῖ I still don't see the solution to his problem though. He is using `virtual` everywhere – Khalil Khalaf Aug 24 '16 at 12:54
  • @JoachimPileborg: could you please expand on that? `C **c = (C**) d; D *d_down = dynamic_cast(c[0]); assert(d_down != NULL);` (i.e. upcasting to `C` instead of `A` before downcasting to `D` again) works just fine, i.e. without the intermediate step of needing to create another array. – Matthias Aug 24 '16 at 12:57
  • @JoachimPileborg, ahhhhh, it took me quite long to realize what you are hinting at, but I am finally at home! Thanks for the hint! – Jan Hudec Aug 24 '16 at 12:59
  • @Matthias If you simply did not do the `(A**)` C-style cast, what compiler error did you get? Didn't that give a clue as to what may / did happen at runtime? – PaulMcKenzie Aug 24 '16 at 13:00
  • It's the extra level of indirection (i.e. that pointer to pointer thing) that is the problem. `D**` is a pointer to a *pointer to `D`*, while `A**` is a pointer to a *pointer to `A`*. There's no way to convert between those two types. – Some programmer dude Aug 24 '16 at 13:02
  • @FirstStep I only meant that `virtual` does solve the _diamond problem_. So that's not the cuplrit. – πάντα ῥεῖ Aug 24 '16 at 13:03
  • @JoachimPileborg object slicing in disguise? – πάντα ῥεῖ Aug 24 '16 at 13:04
  • 1
    That's a very dangerous cast. Consider `a[0] = new C;`. Now your array of `D*` contains something that's not a `D*`. – molbdnilo Aug 24 '16 at 13:09
  • @molbdnilo, yes, it is. That is a good reason why pointers to compatible types are not considered compatible (applies not just to arrays, but also functions and other things). – Jan Hudec Aug 24 '16 at 13:12
  • I've updated the diagram in the answer to match what g++ actually generates in this case. The virtual base is at _negative_ offset. Which is fully expected, as the virtual base can't be made part of the layout of either B1 and B2. The principle applies either way – the pointers are not numerically equal. – Jan Hudec Aug 25 '16 at 06:15
  • Don't use C-style casts. If you get an error with C++-style casts, then that's telling you that the operation is not right, and suppressing the error message won't fix it. – M.M Aug 25 '16 at 06:45

1 Answers1

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.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • 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](http://stackoverflow.com/questions/17925124/can-i-cast-a-derived-class-to-a-private-base-class-using-c-style-cast) – M.M Aug 25 '16 at 06:47
  • @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. – Jan Hudec Aug 25 '16 at 06:56
  • what library author? – M.M Aug 25 '16 at 06:59
  • @M.M, any whose private you are busting. – Jan Hudec Aug 25 '16 at 07:14