1

I'm having a bit of confusion trying to understand what's happening here. If a class object calls a method which then in tern destroys the object, then the method should never return and the object should be destroyed right away and the destructor be called immediately.. Right? But instead the code just crashes.. So is an object not allowed to a call a method to delete itself? I'm assuming from how the callstack works, the pointer to the object would be destroyed hence when the function returns back to the caller the pointer is no longer valid when invoking further down the callstack?

class Foo
{
public:
    virtual void Run()
    {
        printf("Entering callback\n");
        RunSomeCallback();
        printf("We're back from callback\n");
    }
    virtual void RunSomeCallback() = 0;
    
    virtual ~Foo()
    {
        printf("We're Destroyed\n");
    }
};

Foo* f = nullptr;

void DestroyFoo();

class Bar : public Foo
{
public:
    virtual void RunSomeCallback()
    {
        printf("Inside callback\n");
        DestroyFoo();
    }
};


void DestroyFoo()
{
    printf("Deleting foo\n");
    delete f;
}
int main()
{
    f = new Bar();
    f->Run();
    return 0;
}

Then the behavior seems to change when I run the code with shared_ptr, but in this case the destructor is called before it prints "We're back from callback", the code runs but is that then undefined behavior?

class Foo
{
public:
    virtual void Run()
    {
        printf("Entering callback\n");
        RunSomeCallback();
        printf("We're back from callback\n");
    }
    virtual void RunSomeCallback() = 0;
    
    virtual ~Foo()
    {
        printf("We're Destroyed\n");
    }
};

std::shared_ptr<Foo> f;

void DestroyFoo();

class Bar : public Foo
{
public:
    virtual void RunSomeCallback()
    {
        printf("Inside callback\n");
        DestroyFoo();
    }
};


void DestroyFoo()
{
    printf("Deleting foo\n");
    f = nullptr;
}
int main()
{
    f = std::make_shared<Bar>();
    f->Run();
    return 0;
}

Order of prints

Entering callback
Inside callback
Deleting foo
We're Destroyed
We're back from callback

So is the object not actually destroyed until all its methods are complete?

Kayla
  • 223
  • 2
  • 14
  • 1
    Object and execution code are different. You are creating and destroying the object, but you are not dynamically creating or destroying executable code. – fana Mar 06 '23 at 03:41
  • The order of output is going to be the same for both of your examples. What difference in the output do you see with the first snippet? – user17732522 Mar 06 '23 at 03:41
  • @fana I understand I'm not dynamically creating the executable code but in the second example in the case of the smart pointer, why is the destructor still called before the callback returns? SHouldn't the callback return then the destructor be called? Since it's a "Smart pointer" – Kayla Mar 06 '23 at 03:42
  • @user17732522 The code just crashes in the first snippit so I can't really see the output – Kayla Mar 06 '23 at 03:42
  • @Kayla It shouldn't crash. What compiler and compiler flags are you testing with? – user17732522 Mar 06 '23 at 03:43
  • I'm also wondering if the first example is undefined behavior and the second example is defined behavior – Kayla Mar 06 '23 at 03:43
  • 1
    What compiler are you using? It may make a difference, including any debug settings that are enabled. Generally, self-deletion should be the _last_ thing that a member function does. In your case, however, you execute more statements after deletion (the call to `printf` before `Run()` returns). If your compiler has added anything such as heap checks around `this`, it might be a problem. – paddy Mar 06 '23 at 03:43
  • @paddy Should I use cout instead? Would that make a difference? – Kayla Mar 06 '23 at 03:44
  • @user17732522 I'm running under M1 mac XCode version 14.2 with default build settings – Kayla Mar 06 '23 at 03:44
  • 1
    No, I'm saying it's not great form for a method to execute any code at all after self-deletion, except returning. That said, I've never seen a crash when doing so, except when trying to access members or do anything with `this` (such as invoking another method). – paddy Mar 06 '23 at 03:45
  • 1
    Related: [Is "delete this" allowed in C++?](https://stackoverflow.com/q/3150942/11082165) – Brian61354270 Mar 06 '23 at 03:48
  • @Brian Would "delete this" be the same as a method calling delete on the object which called the method? – Kayla Mar 06 '23 at 03:48

1 Answers1

5

There is no problem with an object being destroyed while a member function is still executing on it (regardless of where in the call stack).

However, after the object's lifetime has ended with the destructor call inoked from delete, no function, not even the member function that is still running, may access any member of the object and the memory used by the object may be reused by other objects as well. (Technically it is also implementation-defined whether reading this at all is still allowed, but practically that is fine as long as this isn't dereferenced anymore.)

In your example code no members are accessed after delete (including the destructor) is called, so the program has defined behavior and the output you gave is expected. The destructor is called by delete, ending the objects lifetime, and then Foo::Run continues execution without ever touching the object again.

That's true for both code snippets. Setting the std::shared_ptr to nullptr is effectively also just deleteing the object.


If this causes a crash on your system, then I would suspect a compiler bug

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • Ah okay, so as long as I don't touch any class member objects. I should be okay. Am I still allowed to do extra cleanup on objects that are unrelated to the current object? – Kayla Mar 06 '23 at 03:50
  • And I assumed the first was undefined behavior but I'm not sure why the crash is occuring. The second because the crash didn't occur I assumed shared_ptr is doing extra work to allow the functions to continue as expected. – Kayla Mar 06 '23 at 03:52
  • @Kayla Sure, you can do anything you want. Once a member function has begun execution, there is no difference to any other function as long as you ignore implicit and explicit `this`. – user17732522 Mar 06 '23 at 03:52
  • @Kayla are you compiling with address sanitizer (or any similar tool) on? Asan would give false positive on such cases, effectively trigerring a (expected) crash. Not sure if that's your exact case though. – alagner Mar 06 '23 at 07:00