Is it possible to use placement-new to change the type of a polymorphic object?

690 views Asked by At

Is it possible to use placement-new to change the type of a polymorphic object? if not, what exactly in the standard prohibits it?

Consider this code:

#include <new>

struct Animal {
    Animal();
    virtual ~Animal();
    virtual void breathe();
    void kill();
    void *data;
};

struct Dead: Animal {
    void breathe() override;
};

void Animal::kill() {
    this->~Animal();
    new(this) Dead;
}

Is calling "kill" ever legal?

Update: Early comments do not address the (il)legality according to the standard of the programming technique shown here of changing the type of an object by explicit call to destructor and applying placement-new for a new object compatible.

Because there is interest in why would anyone would like to do this, I can mention the use case that led me to the question, although it is not relevant to the question I am asking.

Imagine you have a polymorphic type hierarchy with several virtual methods. During the lifetime of the object, things happen that can be modeled in the code as the object changing its type. There are many perfectly legal ways to program this, for example, keeping the object as a pointer, smart or not, and swapping in a copy of the other type. But this may be expensive: One has to clone or move the original object into another one of a different type just to swap the new in.

In GCC, Clang, and others, changing the type of an object can be as cheap as to simply changing the virtual table pointer, but in portable C++ this is not possible except by constructing in-place an object of a new type.

In my original use case, the object could not be held as a pointer either.

I would like to know what the standard says on the subject of reusing memory.

1

There are 1 answers

4
Igor Tandetnik On BEST ANSWER

[basic.life]/8 If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

...

(8.4) — the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).

In your example, a call to kill() itself may be valid (if it so happens that sizeof(Animal)==sizeof(Dead), which I don't think is guaranteed), but most attempts to use Animal* pointer or Animal& lvalue through which that call was made would trigger undefined behavior by way of accessing the object whose lifetime has ended. Even assuming that the stars align and Animal subobject of Dead perfectly overlays the original location of the original stand-alone Animal object, such a pointer or lvalue is not considered to refer to the former, but to the now-expired latter.