5

(Initial note: this question is not the same question as whether or not it is safe to delete a void pointer, though that issue has some relation to the problem identified in Update 2. The question here is why a base class obtains a different value from this than is obtained by the derived class for the same object. In cases where the derived object will call a suicide method of the base class, the base class must have a virtual destructor, and the pointer being deleted must be of type pointer-to-base class; storing it in a void* isn't a safe way to delete an object from a base class method.)

I have a diamond-shaped multiple inheritance where my child class has two parents that both inherit from the same grand-parent, thus:

class Grand
class Mom : public virtual Grand
class Dad : public Grand
class Child : Mom, Dad

I wrote Mom and Child, but Grand and Dad are library classes I didn't write (that's why Mom inherits virtually from Grand, but Dad doesn't).

Mom implements a pure virtual method declared in Grand. Dad does not. Therefore, Child also implements that same method (because otherwise the compiler would object that Dad's declaration of that method, inherited by Child, had no implementation). Child's implementation merely calls Mom's implementation. Here's the code (I've included code for Dad and Grand, as this is a SSCCE, not the code I'm stuck using that relies on library classes I didn't write):

class Grand
{
public:
    virtual void WhoAmI(void) = 0;
};

class Mom : public virtual Grand
{
public:
    virtual void WhoAmI()
    {
        void* momThis = this;
    }

    //virtual int getZero() = 0;
};

class Dad : public Grand
{
};

class Child : Mom, Dad
{
public:
    void WhoAmI()
    {
        void* childThis = this;
        return Mom::WhoAmI();
    }

    int getZero()
    {
        return 0;
    }
};

int main()
{
    Child* c = new Child;

    c->WhoAmI();
    return 0;
}

Note that the getZero method in Child is never called.

Stepping through execution with the debugger, I see that the address in Child* c is 0x00dcdd08. Stepping into Child::WhoAmI, I see that the address in void* childThis is also 0x00dcdd08, which is what I expect. Stepping further into Mom::WhoAmI, I see that void* momThis is assigned 0x00dcdd0c, which I interpret to be the address of the Mom subobject of my multiply inherited Child object (but I admit I'm a bit out of my depth at this point).

Okay, the fact that Child's this and Mom's this are different doesn't shock me. Here's what does: if I uncomment the declaration of getZero in Mom, and step through all of that again, Mom::this and Child::this are the same!

How can the addition of virtual int getZero() = 0 to the Mom class result in the Mom subobject and the Child object having the same address? I thought maybe the compiler recognized that all of Mom's methods were virtual and its vtable was the same as Child's, so they somehow became the "same" object, but adding more, and different, methods to each class doesn't change this behavior.

Can anyone help me understand what governs when this is the same for the parent and child of a multiply inherited child and when it is different?


Update

I've tried to simplify things to focus as narrowly as I can on the issue of when this has a different value in a parent object than it has in that parent's child object. To do that, I've changed the inheritance to make it a true diamond, with Dad and Mom both inheriting virtually from Grand. I've eliminated all virtual methods and no longer need to specify which parent class's method I am calling. Instead, I have a unique method in each parent class that will let me use the debugger to see what value this has in each parental object. What I see is that this is the same for one parent and the child, but different for the other parent. Moreover, which parent has the different value changes when the order of the parents is changed in the child's class declaration.

This turns out to have catastrophic consequences if either of the parent objects tries to delete itself. Here's code that, on my machine, runs fine:

class Grand
{
};

class Mom : public virtual Grand
{
public:
    void WhosYourMommy()
    {
        void* momIam = this; // momIam == 0x0137dd0c
    }
};

class Dad : public virtual Grand
{
public:
    void WhosYourDaddy()
    {
        void* dadIam = this; // dadIam == 0x0137dd08
        delete dadIam; // this works
    }
};

class Child : Dad, Mom
{
public:
    void WhoAmI()
    {
        void* childThis = this;

        WhosYourMommy();
        WhosYourDaddy();

        return;
    }
};

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

    c->WhoAmI();

    return 0;
}

However, if I change class Child : Dad, Mom to class Child : Mom, Dad, it crashes at run-time:

class Grand
{
};

class Mom : public virtual Grand
{
public:
    void WhosYourMommy()
    {
        void* momIam = this; // momIam == 0x013bdd08
    }
};

class Dad : public virtual Grand
{
public:
    void WhosYourDaddy()
    {
        void* dadIam = this; // dadIam == 0x013bdd0c
        delete dadIam; // this crashes
    }
};

class Child : Mom, Dad
{
public:
    void WhoAmI()
    {
        void* childThis = this;

        WhosYourMommy();
        WhosYourDaddy();

        return;
    }
};

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

    c->WhoAmI();

    return 0;
}

This is a problem when you have a class that includes methods that can delete objects of that class (a "suicide method"), and those methods might be called from derived classes.

But, I think I have found the solution: any base class that includes a method that might delete instances of itself and that might have those methods called from instances of classes derived from that class must have a virtual destructor.

Adding one to the code above make the crash go away:

class Grand
{
};

class Mom : public virtual Grand
{
public:
    void WhosYourMommy()
    {
        void* momIam = this; // momIam == 0x013bdd08
    }
};

class Dad : public virtual Grand
{
public:
    virtual ~Dad() {};

    void WhosYourDaddy()
    {
        void* dadIam = this; // dadIam == 0x013bdd0c
        delete dadIam; // this crashes
    }
};

class Child : Mom, Dad
{
public:
    void WhoAmI()
    {
        void* childThis = this;

        WhosYourMommy();
        WhosYourDaddy();

        return;
    }
};

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

    c->WhoAmI();

    return 0;
}

A number of people I've met are aghast at the idea of an object deleting itself, but it is legal and a necessary idiom when implementing COM's IUnknown::Release method. I found good guidelines on how to use delete this safely, and some equally good guidelines on using virtual destructors to solve this problem.

I note, however, that unless the person who coded your parent class coded it with a virtual destructor, calling any suicide method of that parent class from an instance of a class derived from that parent is probably going to crash, and do so unpredictably. Perhaps a reason to include virtual destructors, even when you don't think you need one.


Update 2

Well, the problem comes back if you add a virtual destructor to both Dad and Mom. This code crashes when it attempts to delete Dad's this pointer, which does not match Child's this pointer:

class Grand
{
};

class Mom : public virtual Grand
{
public:
    virtual ~Mom() {};

    void WhosYourMommy()
    {
        void* momIam = this; // momIam == 0x013bdd08
    }
};

class Dad : public virtual Grand
{
public:
    virtual ~Dad() {};

    void WhosYourDaddy()
    {
        void* dadIam = this; // dadIam == 0x013bdd0c
        delete dadIam; // this crashes
    }
};

class Child : Mom, Dad
{
public:
    virtual ~Child() {};

    void WhoAmI()
    {
        void* childThis = this;

        WhosYourMommy();
        WhosYourDaddy();

        return;
    }
};

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

    c->WhoAmI();

    return 0;
}

Update 3

Thanks to BeyelerStudios for asking the right question: deleting a void* instead of deleting a Dad* prevented C++ from knowing what it was really deleting and, therefore, stopped it from calling the virtual destructors of the base and derived classes. Replacing delete dadIam with delete this solves that problem, and the code runs fine.

Although it would be somewhat ridiculous, replacing delete dadIam with delete (Dad*)dadIam also runs fine, and helps illustrate that the type of the pointer operated on by delete makes a difference to what delete does. (Something I should hardly find surprising in a polymorphic language.)

BeyelerStudios, if you want to post that as an answer, I'll check the box for you.

Thanks!

Stevens Miller
  • 1,387
  • 8
  • 25
  • This is the Diamond Problem right? I assume that your child needs to over ride all. Look it up on how to solve the Diamond Problem – Khalil Khalaf Jul 08 '16 at 12:54
  • 1
    Standard allows the compiler to put the v-table (which is the normal way of implementing polymorphic types) anywhere it likes, including at the start of the structure. So there's no hard-and-fast rule. Essentially it's down to the compiler. Perhaps tag this question with a specific toolset? – Bathsheba Jul 08 '16 at 12:55
  • Probably not related at all, but note that `Child` still has two `Grand` parents due to there being only one virtual inheritance. – molbdnilo Jul 08 '16 at 13:11
  • @FirstStep , yes, it is a diamond inheritance, but not the classic "diamond problem," which I've (necessarily) avoided by having Child implement the abstract method from Grand that Dad does not implement. – Stevens Miller Jul 08 '16 at 13:13
  • Good idea, @Bathsheba . I've added the VS2015 tag. – Stevens Miller Jul 08 '16 at 13:14
  • @molbdnilo , I'm not sure that isn't related. You're right, there are two `Grand` subobjects here. This whole mess came up because I can't force there to be just one. In actual practice, what I'm doing is subclassing COM's IUnknown to Mom, and its IFactory to Dad. Alas, IFactory subclasses IUnknown, but not virtually, so I am stuck with Child having to implement all of IUnknown's methods, when I wanted to do that in Mom. One of those methods needs to access `this`, which is how i found out it isn't always the same for subobjects. – Stevens Miller Jul 08 '16 at 13:18
  • 1
    I thought you said you couldn't change Dad. If you have any virtual functions, you need a virtual destructor in the base (and therefore all children). Also rule of three. As for getting a consistent this pointer in Child and Mom, have you tried (Mom*) this? That gives me a consistent value (on one compiler). – Kenny Ostrom Jul 08 '16 at 15:00
  • I don't have any problem with self-deleting objects, in principle, but you can't do that with virtual objects unless you guarantee that you always call the right destructor. That's the intended job of the virtual destructor, which you don't have, so you either MUST NOT do that, or you must find some other way to be sure you always call the right class's destructor. – Kenny Ostrom Jul 08 '16 at 15:04
  • @KennyOstrom , in the first part of my post, `Dad` was a stand-in for `IClassFactory`, which I can't change. In the update, I've shown how the problem can arise more generally. It would be easy to move the crash from `Dad` code to `Mom` code, and then illustrate the fix in the `Mom` class, that's just not how I did it. Your advice about virtual methods calling for virtual destructors in the base is good (and endorsed by the site I linked to). Note, however, that my example still requires a virtual destructor, even though it has no virtual methods. – Stevens Miller Jul 08 '16 at 15:08
  • Yes, virtual destructors are the key. I also get matching `this` values for `Child` and `Mom` as well, depending on the order in which `Mom` and `Dad` are named in `Child`'s declaration. Nothing one should rely upon, of course. Before discovering that virtual destructors address this issue, I was thinking of writing code that passed `this` up and down the inheritance hierarchy. Praise Ghu it now looks like that's not necessary. – Stevens Miller Jul 08 '16 at 15:11
  • Well, looks like I spoke too soon, Virtual destructors didn't solve the problem, at least, not after adding one to both parent classes. See Update 2, above. – Stevens Miller Jul 08 '16 at 16:28
  • 1
    Possible duplicate of [Is it safe to delete a void pointer?](http://stackoverflow.com/questions/941832/is-it-safe-to-delete-a-void-pointer) – BeyelerStudios Jul 08 '16 at 16:41
  • 1
    Why are you deleting a `void*` and not `this`? – BeyelerStudios Jul 08 '16 at 16:41
  • Ah, @BeyelerStudios , you nailed it! Not a duplicate (see my note at the top of the OP), but the final bug was due to deleting a `void*`, when I should have been deleting a `Dad*` (which `this` is). Great help you guys, thanks! – Stevens Miller Jul 08 '16 at 17:03
  • 1
    Your question asks why the address is different base vs child. @Bathsheba is right: this is expected behavior (on many systems) because the compiler can put the vtable wherever the hell it feels like, and this becomes visible once you have multiple inheritance from more than one base classes which each have virtual methods (diamond not necessary, virtual inheritance not necessary). – davidbak Jul 08 '16 at 17:12

1 Answers1

0

As mentioned by the standard [intro.object]:

Objects can contain other objects, called subobjects. A subobject can be [...] a base class subobject [...].

Moreover [expr.prim.this]:

The keyword this names a pointer to the object for which a non-static member function is invoked [...].

It goes without saying that two different classes (derived and base) are different objects, thus can have different values for the this pointer.

Can anyone help me understand what governs when this is the same for the parent and child of a multiply inherited child and when it is different?

When and why they differ is not ruled by the standard (of course, it's mainly due to the existence of a vtable associated to the object, but please note that vtables are simply a common, convenient way to deal with polymorphism and the standard never mentions them).
It usually derives from the chosen/implemented ABI (see here for further details about a common one, the Itanium C++ ABI).

It follows a minimal, working example to reproduce the case:

#include<iostream>

struct B {
    int i;
    void f() { std::cout << this << std::endl; }
};

struct D: B {
    void f() { std::cout << this << std::endl; }
    virtual void g() {}
};

int main() {
    D d;
    d.f();
    d.B::f();
}

An example output is:

0xbef01ac0
0xbef01ac4

skypjack
  • 49,335
  • 19
  • 95
  • 187