Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 510
Toby Brull Avatar asked Oct 21 '17 07:10

Toby Brull


People also ask

Can we use this pointer in destructor?

In one word: YES.

Can we pass arguments in destructor?

A destructor takes no arguments and has no return type. Its address cannot be taken. Destructors cannot be declared const , volatile , const volatile or static . A destructor can be declared virtual or pure virtual .

What does the destructor for pointers do?

It destroys the object that's pointed to (using its destructor, if it has one, and doing nothing otherwise), and deallocates the memory that's pointed to. You must previously have used new to allocate the memory and create the object there.

Can we call function in destructor?

Destructors can freely call class member functions and access class member data.


2 Answers

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

like image 148
Passer By Avatar answered Jan 04 '23 12:01

Passer By


Yes, this is legal, since the master object will not be destroyed before the termination of execution of the destructor.

However, this is not a good practice in general.

like image 20
gsamaras Avatar answered Jan 04 '23 12:01

gsamaras