Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert array of pointers of derived class to array of base class pointers

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?
like image 318
Matthias Avatar asked Dec 10 '22 15:12

Matthias


1 Answers

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.

like image 101
Jan Hudec Avatar answered Dec 13 '22 06:12

Jan Hudec