Is overriding destructor for automatic object well defined?

Viewed 88

I had a situation where I wanted to import a call after another call from the calling function. I decided to override a virtual destructor for the purpose:

#include <iostream>

struct type {
    virtual ~type() {
        std::cout << "ordinary" << std::endl;
    }
    void method() {
        struct method_called : type {
            virtual ~method_called() override {
                std::cout << "method called" << std::endl;
            }
        };

        this->~type();

        new (this) method_called{};
    }
};

int main() {
    
    std::cout << "ordinary expected" << std::endl;

    {
        type obj;
    }

    std::cout << "method expected" << std::endl;

    {
        type obj;

        obj.method();
    }

    std::cout << "method expected" << std::endl;

    type* pobj = new type{};

    pobj->method();

    delete pobj;
}

It seems the overridden destructor is called only using dynamic allocation. Is this intended?

GCC godbolt.

1 Answers
this->~type();

Any dereference of any pointer pointing to the object or variable referring to the object that exists prior to this line is undefined behavior once you do this.

This includes the automatic storage destructor.

To avoid undefined behaviour in main after doing this, you'd have to call exit or similarly never return from the scope where the variable exists.

By the time the destructor is finished, the objects lifetime is over. Almost any use of the object after that triggers UB. You can reuse the storage:

    new (this) method_called{};

This creates an distinct object in the storage that this refers to. If this storage has sufficient alignment and size, doing so is fine. It is extremely tricky to get right, because one of the few ways to safely get a pointer to this newly created object is by recording the return value to placement new like this:

auto* pmc = new (this) method_called{};

you don't do that.

{
    type obj;

    obj.method();
}

this is undefined behavior. The standard does not restrict what the program produced by your compiler does if this code is or would be executed at any point in your programs execution.

type* pobj = new type{};

pobj->method();

delete pobj;

so is this.

Related