Question

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.

Was it helpful?

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.

OTHER TIPS

I'm pretty sure that is not valid code for several reasons:

  1. If the inserted type isn't the same size bad things will happen (I'm not quite sure but I think the standard doesn't make to much promises about the size of a type, so from the theoretical vantage point it might be hard to prove that they have the same size (through in praxis they most likely will))
  2. if the type of the variable is statically known (probably due to it beeing constructed on the stack, but it in theory it could do the same thing if it can see the allocaiton and prove that the pointer couldn't have been modified) the compiler can feel free to statically reolve virtual method calls (e.g. a destructor) and use them which would obviously break the code
  3. Even if the type of the variable is not statically known I'm quite sure the compiler can assume that it's type won't change during it's lifetime (The pointer can't change inside the function so it should be able to assume the pointed to type doesn't either). Therefore while it can't statically resolve methods it could possibly reuse the vmt pointer from previous invocations of virtual methods (e.g. the one changing the type)

Edit: now that I think about it doesn't this break strict aliasing rules, since after the placement new this points to a non compatible type? granted it isn't explicietly accessed in the function again, but I don't think it can be guranteed that there won't be accesses inserted by the compiler (highly unlikely though). Anyways this would mean that the compiler can assume that this kind of action won't happen.

Edit: When looking at the new C++ Standard I found that [basic.life] (§3.8.5) gives what is basically the same thing as an example of undefined behaviour (it doesn't actually destroy the object, but I don't see how that could make matters better):

#include<cstdlib>
structB{
    virtual void f();
    void mutate();
    virtual ~B();
};
struct D1:B { void f(); };
struct D2:B { void f(); };
void B::mutate(){
    new(this)D2; //reuses storage—ends the lifetime of *this
    f(); //undefined behavior
    ...=this; //OK, this points to valid memory
}
void g(){
    void* p = std::malloc(sizeof(D1) + sizeof(D2));
    B* pb = new(p)D1;
    pb->mutate();
    &pb; //OK: pb points to valid memory
    void* q = pb; //OK: pb points to valid memory
    pb->f(); //undefined behavior, lifetime of *pb hasended
}

This should prove that this is not allowed.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top