class C
{
public:
virtual ~C() {}
virtual void switch_me() = 0;
};
class C1 : public C
{
public:
C1() : b(true) { std::cout << "C1\n"; }
~C1() { std::cout << "~C1\n"; }
private:
void switch_me();
bool b;
};
class C2 : public C
{
public:
C2() : i(1) { std::cout << "C2\n"; }
~C2() { std::cout << "~C2\n"; }
private:
void switch_me();
int i;
};
void C1::switch_me()
{
this->~C1(); // lifetime of *this ends here
std::cout << "blih\n"; // execute some code that does
// not attempt to access object
new(this) C2(); // create a C2 instance in-place
}
void C2::switch_me()
{
this->~C2(); // lifetime of *this ends here
std::cout << "blah\n"; // execute some code...
new(this) C1(); // create a C1 instance in-place
}
class Cnt
{
public:
Cnt() { new(&storage) C1(); }
~Cnt() { (*this)->~C(); }
C* operator->() { return reinterpret_cast<C*>(&storage); }
private:
char storage[std::max(sizeof(C1),sizeof(C2))];
};
int main()
{
Cnt c;
c->switch_me();
c->switch_me();
return 0;
}
You do not have Undefined Behavior regarding switch_me
function: you do not access object in any way after destruction, and next access happens on new object. You might have UB if you save pointer and reference to C
object returned vy operator->
and use it after call to switch_me
per 3.8/7:
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:
- the storage for the new object exactly overlays the storage location which the original object occupied, and
- the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
- the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
- 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).
You do have UB in other place, namely, your storage. It has weaker alignment than object you want to place in it, and that can cause alignment issues. Use alignas
keyword to specify desired alignment:
alignas(C1) alignas(C2) char storage[std::max(sizeof(C1),sizeof(C2))];
If two alignment specifiers are applied to same declaration, larger is used.