Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does upcasting a child pointer to a base class sometimes change the pointer value?

I found a few other questions that seem to implicate my question, but none directly answered it (even the one with almost the same name).

I have a C++ child class derived from two parents. I create an object of that class, then cast its reference to each of its two parents. The cast values are not the same, though one remains the value of the child reference. Here's the code:

class Mom
{
public:
    int a = 1;
};

class Dad
{
public:
    int b = 2;
};

class Child : public Mom, public Dad
{
public:
    int c = 3;
};

int main()
{
    Child* c = new Child; // 0x00C5A618

    Mom* m = c; // 0x00C5A618
    Dad* d = c; // 0x00C5A61C

    return 0;
}

Now, I am guessing that the addresses obtained by applying static_cast<> are those of the actual subobjects. As it happens, Mom is the first subobject, so it actually has the same address as Child. Dad is the second subobject, so it resides at a different address.

As I understand it, C-style casts, like this

Mom* m = (Mom*)c;
Dad* d = (Dad*)c;

will apply a static_cast<> when it would be legal to do so. Indeed, when I actually compile and execute that version, I see the same behavior as before.

Continuing this scintillating saga, using implicit casts, like this

Mom* m = c;
Dad* d = c;

exhibit the same behavior as well, from which I infer that implicit casts also apply static_cast<> when the equivalent explicit cast would be legal.

Downcasting back to Child does the same thing:

Child* k = static_cast<Child*>(d); // 0x00C5A618

The value in k is different from the value in d, having been set back to the value originally in c.

I am guessing that, in this case, there is a run-time check to see if, indeed, d points to a Dad subobject of a Child object.

So far, this all makes sense, but I'm surprised that there is no such address adjustment (if that's the right thing to call it) for singly derived hierarchies. That is, if my hierarchy is simply Mom->Dad->Child, casting a reference to a Child into a pointer to either a Mom or a Dad never changes the value:

class Mom
{
public:
    int a = 1;
};

class Dad : public Mom
{
public:
    int b = 2;
};

class Child : public Dad
{
public:
    int c = 3;
};

int main()
{
    Child* c = new Child; // 0x00C5A618

    Mom* m = c; // 0x00C5A618
    Dad* d = c; // 0x00C5A618

    Child* k = static_cast<Child*>(d); // 0x00C5A618

    return 0;
}

I'd have expected it to change by the size of the the storage needed for each additional subclass, or four bytes (the size of an int). But, they don't. All of the pointers in the above example, c, m, d, and k, have the same value.

So, now I am guessing that the objects were laid out in memory with the Mom object at the lowest address, the additional space needed for the Dad object coming next, and the additional space needed for the Child coming last. Since a Dad is-a Mom, the Dad address would be the same as the Mom address, wouldn't it?

Likewise, a Child is-a Mom too, so its address would also be the same as its own Mom subobject's address.

So...

I think it is working like this: in memory, the objects follow each other such that, in purely singly derived hierarchy, upcasting and downcasting always yields the same address, while in a multiply derived hierarchy, some upcasts yield a different address, because not all subobjects will start at the same address as the derived object. Like this:

Possible Memory Layout for a Singly Derived Hierarchy

+-------+-----+-----+---+
| Child : Dad : Mom : a |
|       |     +-----+---+
|       |           : b |
|       +-----------+---+
|                   : c |
+-----------------------+


Possible Memory Layout for a Multiply Derived Hierarchy

+-------+-----+---+
| Child : Mom : a |
|       +-----+---+
|       : Dad : b |
|       +-----+---+
|             : c |
+-----------------+

Sorry that I've rambled a bit, but my question is this: is the fact that an upcast to some base classes of a multiply derived object changes the value of the reference a result of the fact that the post-cast value is the address of the base object in memory?

like image 532
Stevens Miller Avatar asked Jul 10 '16 22:07

Stevens Miller


People also ask

Does casting a pointer change its value?

A cast does not change the value of an object. While technically correct, your answer is misleading. Casting a pointer to a class instance may return a different pointer address than the casted pointer contains.

What is Upcasting and downcasting in C?

Upcasting can cause object slicing when a derived class object is passed by value as a base class object, as in foo(Base derived_obj). Downcasting. The opposite process, converting a base-class pointer (reference) to a derived-class pointer (reference) is called downcasting.

Is Upcasting safe in C++?

Upcasting is safe casting as compare to downcasting. It allows the public inheritance that implicitly cast the reference from one class to another without an explicit typecast.

What is Upcasting and downcasting which keyword is used to achieve these in C++?

C++ allows that a derived class pointer (or reference) to be treated as a base class pointer. This is upcasting. Downcasting is an opposite process, which consists of converting base class pointer (or reference) to derived class pointer.


1 Answers

Yes, you are right. Upcasting a pointer gives the address of the parent subobject which is stored somewhere inside the child class. Usually the parent subobject is stored at the beginning of the child object, so pointers to the child and base subobject are the same.

In case of multiple inheritance, it is impossible for all parents to occupy the same memory (let's ignore possible Empty Base Optimization), so all, but one, base class addresses will have to differ from the child address.

like image 200
Revolver_Ocelot Avatar answered Oct 28 '22 23:10

Revolver_Ocelot