Search code examples
c++templatesvirtualplacement-new

delete this and placement new of virtually derived class


class base {
    int a;
protected:
    template<class T>
    class derived;
public:
    base() {}
    virtual ~base() {}
    virtual void func() {}
    static base* maker();
};

template <class T>    
class base::derived 
    : public base
{
public: 
    derived() {}
    virtual ~derived() {}
    virtual void func() {
        this->~derived(); //<--is this legal?
        new (this) derived<int>(); //<--is this legal?
    }
};

base* base::maker() {
    return new derived<double>();
}

int main() {
    base* p = base::maker(); //p is derivedA<double>
    p->func(); //p is now derivedA<int>
    delete p; //is the compiler allowed to call ~derived<double>()?
}

This is a Short, Self Contained, Correct (Compilable), Example of my code (which is basically reinventing any_iterator for my own growth).

The question reduces down to: is it undefined behavior to destroy this and reconstruct this with a different type virtually derived from the same base, when neither has any additional members over the shared base? Specifically, are compilers allowed to call keep track of the static type, or is that technically nonconforming?

[EDIT] Several people pointed out that compilers may call the incorrect destructor if derivedA is created on the stack. (1) I can't find anything in the standard that allows compilers to do so. (2) That was aside from what I intended by my question, so I have changed the code to show that derived cannot be placed on the stack. base can still be on the stack though.


Solution

  • I think that's clearly not OK.

    As a preface, and object's lifetime can indeed be ended by calling the destructor, but you're only allowed (and required) to construct a new object of the same type in its place:

    {
      Foo x;
      x.~Foo();
      ::new (&x) Foo;
    }  // x.~Foo() must be a valid call here!
    

    Remember that at scope's end the destructor will be invoked!

    But in your case you're constructing an entirely different object:

    ::new (&x) Bar;   // Bar = DerivedA<int>
    

    Clearly if sizeof(Bar) exceeds sizeof(Foo) this cannot be OK.

    (Perhaps if you can make additional guarantees on the object sizes, as well as on the alignment guarantees, we can think about this further.)

    Update: Even if you're thinking, OK, so those types are derived from the same base, so invoking the destructor brings virtual happiness, I'm still pretty sure that that's a violation. In this static setting, the compiler may well resolve the virtual call statically, so you're breaking the compiler's assumptions if you change the dynamic type of the thing pointed to by &x.

    Update 2: Another thought on the same matter: The static type of *&x is known, and I think you have to respect that. In other words, the compiler has no reason to factor in the possibility that the static type of a local variable changes.