Is there an implicit pointer to base class subobject when accessing one of its members?

337 views Asked by At

If we had this code:

class Base
{
int anint;
float afloat;
};
class Derived : Base
{
//inherited member variables...
};

I have been told that members of Base would be inherited to Derived and these the inherited members in Derived are actually inside a base class subobject of Base (but this subobject is unnamed); a subobject of Base is created in Derived that holds the members that are inherited. So when accessing a member in a class, there is an implicit invocation to the this pointer, unless you do something explicitly, but is there also an implicit pointer (or anything) invoked when accessing an inherited object? Like, if we accessed anint in an instance of Derived by derivedInstance->anint, would this actually look like derivedInstance->this->somethingToBaseObjectThatHoldsTheMembers->anint or how does this work?

2

There are 2 answers

0
sehe On

No. The compiler choses a layout (ABI). Static casts leverage knowledge of that layout to adjust pointers using static_cast.

RTTI enables dynamic pointer adjustments using dynamic_cast.

See e.g. Regular cast vs. static_cast vs. dynamic_cast

0
Justin Finnerty On

There is no special pointer to base class data. The layout of data members in C++ follows closely from C. (In fact your example has no virtual methods so must follow C exactly). So lets consider how your code would look in C:

 struct Base
 {
    int anint;
    float afloat;
 };
 struct Derived
 {
   struct Base; // C style inherit from struct Base
   int a2ndInt;
 };

In C, the structure memory layout is defined to be pretty much as you write it. This means that the struct Derived has basically the following memory layout.

struct Derived
{
    int anint;
    float afloat;
    int a2ndInt;
};

The this pointer is to the start of the struct, so accessing anint or afloat from either a pointer to Derived or Base involves the same memory offset. Thus down casting is always easy here.

Things get more complicated when you have virtual functions as the data structure then has to have a hidden pointer to its virtual functions, but there need only be one such pointer. Lets consider the single inheritance case and you might imagine a layout something like (the actual layout depends on the ABI):

 struct Base
 {
    <ABI defined pointer type> * class_; // hidden virtual function table
    int anint;
    float afloat;
 };
 struct Derived
 {
   struct Base; // inherit from struct Base
   int a2ndInt;
 };

Now the struct Derived might have the following memory layout. Note that when creating a Derived object the constructor must set the class_ pointer. This is one reason why the constructors start from the Base class constructor, as each Derived class can then override the class_ pointer.

struct Derived
{
    <ABI defined pointer type> * class_; 
    int anint;
    float afloat;
    int a2ndInt;
};

So again accessing anint or afloat from either a pointer to Derived or Base involves the same offset. Thus down casting is again easy here.

Multiple inheritance is much more complicated and is where static_cast<> for down casting is essential. This case is closest to what you are thinking but still only involves an offset to a single this pointer.

struct Base1
{
   <ABI defined pointer type> * class_; // hidden virtual function table
   int anint;
};
struct Base2
{
   <ABI defined pointer type> * class_; // hidden virtual function table
   float afloat;
};

I am not so familiar with the ABI but I imagine the hidden virtual table pointers could be merged somehow resulting in a memory layout something like:

struct Derived
{
    <ABI defined pointer type> * class_; // merged Base1 and Base2
    int anint;
    float afloat;
    int a2ndInt;
};

or not merged (depending on the ABI)

struct Derived
{
    <ABI defined pointer type> * class_; // from Base1
    int anint;
    <ABI defined pointer type> * class_; // from Base2
    float afloat;
    int a2ndInt;
};

So again accessing anint from a pointer to Derived or Base1 involves the same offset, but accessing afloat does not work. This means that using a C-style cast (i.e. using (Base2*)) from a Derived pointer to a Base2 pointer fails, you need static_cast<> which handles the change in offset.

Note that this is not so complicated if only one of the base classes has member data. This is why it is often recommended that, when using multiple inheritance, only one of the base classes should have data.

NOTE: The real ABI defines the true layout of data in the structure. What is shown here is for illustration purposes only.