C++ Pointer Multi-Inheritance Fun

more c++ multiple inheritance fun

  • Yes
  • Yes
  • No
  • Yes
  • No*

But you need to know how C++ organises memory. The layout of a class, CClass, is as follows:

offset
0 First base class in list of base classes
sizeof first base class Next base class
sizeof first N-1 base classes Nth base class
sizeof all base classes CClass

OK, it's a bit more complex than that if there is an inheritance tree, but you should get the basic idea. Assuming an int is four bytes then class C is laid out like:

offset
0 A::x
4 A::y
8 B::xx
12 B::yy
16 C:z

But the an object of type C is all of the above, so a pointer to a C is pointing to offset 0. However, C++ allows implicit downcasting, so a pointer to a C can be converted to a pointer to an A or a pointer to a B. But if you look at the above, a pointer to a B is at offset 8 rather than 0 (a pointer to a C) and that a pointer to an A is at offset 0. So, casting a pointer to a C to a pointer to a B adds 8 to the pointer value. This is because the methods of B assume the 'this' pointer points to B's first member (B::xx), but a pointer to a C if reinterpreted as a pointer to a B (i.e. the value is the same) would be pointer to an address eight bytes before where B actually is, so that all of B's methods would be using, in this instance, A's members.

Upcasting (the final two conversions) is different kettle of fish. Going from a pointer to a B to a pointer to a C is really hard because you don't know if the pointer to a B is just pointing to an instance of B or at an instance of C plus eight. This is where RTTI and the dynamic cast comes in. With RTTI (Run Time Type Information) enabled, the pointer to B contains additional information that describes what B really is - a simple B or a B as part of a C. This does have additional cost, both execution time and memory usage.

Finally, this does highlight the ambiguity of the C style cast. You really should use the C++ style casts (static_cast <>, etc) as this clarifies the way the conversion should be done.

*This could also be a yes, I guess it depends on the compiler and if RTTI is on or off. You would need to get into the details of the standard and the compiler implementation to say for sure.

Multiple inheritance pointer comparison

Well, no, it won't work.

I'm personally a big fan of learning-by-example, so here's one:

#include <iostream>

class Base1
{
public:
Base1()
{
numberBase1 = 1;
}

int numberBase1;
};

class Base2
{
public:
Base2()
{
numberBase2 = 2;
}

int numberBase2;
};

class Derived : public Base1, public Base2
{
public:
Derived()
{
numberDerived = 3;
}

int numberDerived;
};

int main()
{
Derived d;
Base1 *b1 = &d;
Base2 *b2 = &d;

std::cout << "d: " << &d << ", b1: " << b1 << ", b2: " << b2 << ", d.numberDerived: " << &(d.numberDerived) << std::endl;

return 0;
}

One run-through on my computer outputted this:

d: 0035F9FC, b1: 0035F9FC, b2: 0035FA00, d.numberDerived: 0035FA04

Soo.. If we define the address of d as 0, then b1 is 0, b2 is +4 and the number of d is +8. This is because an int on my machine is 4 byte long.

Basically, you have to look at the layout of how C++ internally represents a class:

Address:    Class:
0 Base1
4 Base2
8 Derived

.. So in total, instantiating a Derived class will allocate space for the base classes of the derived class, and finally make room for the derived object itself. Since we have 3 integers here, that'll be 12 bytes.

Now, what you're asking (unless I misunderstood something) is if you can compare the address of the different base class pointers to each other to see if they point to the same object, and the answer is no - Not directly at least, as in my example, b1 would point to 0035F9FC, while b2 would point to 0035FA00. In C++, this offsetting is all done at compile time.

You could probably do some magic with RIIA and sizeof() and determine how much of an offset b2 should have to be comparable to b1, but then you run into all kinds of other trouble like virtuals. In short, I would not recommend this approach.

A much better way would be to cast to Derived* like ialiashkevich said, however, that would impose a problem if your object was not an instance of Derived*.

(Disclaimer; I haven't used C++ in 3-4 years, so I might be a bit off my game. Be gentle :) )

C++ comparing pointers using multiple-inheritance with unknown base class

If your classes are polymorphic (have at least one virtual function) then dynamic_cast<void *>() will return a pointer to the beginning of the most-derived object - so it will always return the same answer for the same object even if you start from pointers to unrelated base classes.

But if you have already converted the pointer you had to void * it's too late for that. You might be better off just storing pointers to some common base class.

Base pointer offset adjustment for multiple inheritance question

The caller of a function knows exactly what types are involved and does the necessary adjustments.

That is,

Child c;
fun(&c);

behaves exactly the same as

Child c;
fun(static_cast<Father*>(&c));

but the conversion is implicit.

c++ multiple-inheritance

You don't need any function, since C only derives once from A and B. Unless you derive from A or B multiple times (without virtual inheritance), you only need to use:

A *pbb = pc;
B *pba = pc;

AtoC and BtoC are only safe through:

C *c = dynamic_cast<C*>(a_or_b_pointer);

C++ multiple inheritance and upcasted smart pointer destruction causes heap corruption in VS 2017

This breaks because the pointer you pass to delete is not the same that you got back from new.

Up-casting basically means you take a pointer-to-derived and pretend it is a pointer-to-base. For single inheritance, this just works, as the base-part is always the first thing that is stored in a derived object.
But with multiple inheritance, you have two bases!

Hence, when you up-cast to the second base, you actuall need to change the value of your pointer to make sure that what it's pointing to will actually be the respective base part of your object. You can verify that by examining the values of the pointers in the debugger:

Dog* d = new Dog;
Animal* a = d;

The a pointer will point one byte behind the d pointer.

As was already mentioned, this can be fixed by adding a virtual destructor to the base class type that you are using in the delete call (Animal in your example). This will cause the compiler to generate additional code to re-adjust the pointer correctly before passing it to delete.

Note that gcc actually implements the empty-base class optimization here, so this example will work there. Both bases will live at the same offset. It will start breaking there as well as soon as you start adding non-static data members to the base classes.

reinterpret_cast used in multi inheritance in C++

Base class subobjects have to be somehow arranged in memory inside the most derived object. In your case, it seems the compiler did this:

C---------------+
|A---+ B---+ |
||m_i| |m_d| m_c|
|+---+ +---+ |
+---------------+

Which means the A subobject starts at the same address as the entire C object, but the B subobject is shifted by the size of A (i.e. the size of an int).

B *b2 = &d;

Because b2 is a pointer to B, it points to the B subobject, thus the physical address it points to is the shifted one.

As for why b2 == &d holds - pointers to different types can never be compared for equality. But a pointer to derived class can be implicitly converted to a pointer to base class. So b2 == &d is actually converted to b2 == static_cast<B*>(&d), which holds. Note that the implicit conversion naturally applies the shift, and it's the same one you used to obtain b2 in the first place.

You can experiment with reorganising the inheritance order or removing/adding some data members to see how it affects layout.



Related Topics



Leave a reply



Submit