Search code examples
c++c++11thisdestructorobject-lifetime

Passing the "this" pointer to other class/function in destructor


Is it legal C++ to create a worker-object on the stack in the destructor of some master-object and pass the this pointer of the master-object to the helper-object? The helper-object would then also call member functions of the master-object or access member-variables.

In other words, is the following legal C++?

struct MasterClass
{
  MasterClass (int data);

  ~MasterClass ();

  int data;
};

struct WorkerClass
{
  WorkerClass (MasterClass *m) : m (m) { }

  void do_some_work () { m->data = 42; }

  MasterClass *m;
};

MasterClass::MasterClass (int data)
: data (data)
{ }

MasterClass::~MasterClass ()
{
  WorkerClass w (this);

  w.do_some_work ();
}

int main ()
{
  MasterClass m (7);
}

I understand that the lifetime of the master-object ends once the destructor begins to execute. But I believe it is legal to call non-virtual member functions in the destructor of any object, which make use of the implicit this argument/parameter.


Solution

  • Yes and no.

    Yes, because its legal in this very short example you've shown.

    No, because it might result in UB, there are some caveats surrounding usage of an object during destruction

    TLDR It's always fine if you don't have any inheritance.

    Now, for the cases where it is not fine to use an object during destruction.

    The following cases will assume the following is already written

    struct V;
    struct A;
    struct B;
    struct D;
    
    void foo(A* a = nullptr);
    
    struct V {
        virtual void f();
        virtual void g();
    };
    
    struct A : virtual V {
        virtual void f();
    };
    
    struct B : virtual V {
        virtual void g();
        ~B() {
            foo();
        }
    };
    
    struct D : A, B {
        virtual void f();
        virtual void g();
        ~D() {
            foo(this);
        }
    };
    
    int main() {
        D d;
    }
    

    Calling virtual functions

    Upon the destruction of x (aka as soon as its destructor is called)

    If the virtual function call uses an explicit class member access and the object expression refers to the complete object of x or one of that object's base class subobjects but not x or one of its base class subobjects, the behavior is undefined.

    Which means, if you use a explicit class member access to call a virtual function with a pointer pointing to the entirety of x, but somehow the pointer isn't the type of x nor its bases, the behaviour is undefined.

    void foo(A* a) {
        static auto ptr = a;
        ptr->g();  // UB when called from ~B
                   // ptr refers to B, but is neither B nor its base
    }
    

    Using typeid

    If the operand of typeid refers to the object under construction or destruction and the static type of the operand is neither the constructor or destructor's class nor one of its bases, the behavior is undefined.

    Likewise, if the operand refers to the object being destructed, yet somehow isn't the object and its bases, the behaviour is undefined.

    void foo(A* a) {
        static auto ptr = a;
        typeid(*ptr);  // UB when called from ~B()
                       // ptr refers to B, but is neither B nor its base
    }
    

    Using dynamic_cast

    If the operand of the dynamic_­cast refers to the object under construction or destruction and the static type of the operand is not a pointer to or object of the constructor or destructor's own class or one of its bases, the dynamic_­cast results in undefined behavior.

    Same deal.

    void foo(A* a) {
        static auto ptr = a;
        dynamic_cast<B*>(ptr); // UB when called from ~B()
                               // ptr refers to B, but is neither B nor its base
    }
    

    Conclusion

    Now, if you think this is a fiasco and didn't understand what is going on, just don't pass this anywhere in a destructor.

    All quotes from http://eel.is/c++draft/class.cdtor