I was looking to re-use allocated space within the base class from this pointer and C++ Standard does not approve. However, the wording of the standard seems to be wrong. It puts a condition "and before the storage which the object occupied is reused or released", but it is clearly reused in their own code snippet. Where I am getting it wrong?
void B::mutate() {
new (this) D2; // reuses storage — ends the lifetime of *this!! REUSED AS WELL SO CONDITION SO RESTRICTIONS DON'T HOLD ANYMORE!
f(); // undefined behavior
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated41 or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see [class.cdtor]. Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.deallocation]), and using the pointer as if the pointer were of type void*, is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if:
(6.1) the object will be or was of a class type with a non-trivial destructor and the pointer is used as the operand of a delete-expression,
(6.2) the pointer is used to access a non-static data member or call a non-static member function of the object, or
(6.3) the pointer is implicitly converted ([conv.ptr]) to a pointer to a virtual base class, or
(6.4) the pointer is used as the operand of a static_cast, except when the conversion is to pointer to cv void, or to pointer to cv void and subsequently to pointer to cv char, cv unsigned char, or cv std::byte ([cstddef.syn]), or
(6.5) the pointer is used as the operand of a dynamic_cast.
[ Example:
#include <cstdlib> struct B { virtual void f(); void mutate(); virtual ~B(); }; struct D1 : B { void f(); }; struct D2 : B { void f(); }; /* RELEVANT PART STARTS */ void B::mutate() { new (this) D2; // reuses storage — ends the lifetime of *this f(); // undefined behavior /* RELEVANT PART ENDS */ ... = 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 has ended }
When you do
f();
in a member function what you are really doing is
this->f();
So in the example when they do
new (this) D2;
it ends the lifetime of the thing pointer to by this
and creates a new D2
in it's place. That makes you think that this->f();
is okay since this
now points to an object that has had it's lifetime started but you are forgetting that this
is a pointer to an object that has had it's lifetime ended. You can't use it to refer to the new object that you made.
In order to be able to call f()
legally what you would need to do is capture the pointer returned by new
and use it to access the new object
void B::mutate() {
auto np = new (this) D2; // reuses storage — ends the lifetime of *this
f(); // undefined behavior**
np->f() // OK, np points to a valid object
... = this; // OK, this points to valid memory
}
but it is clearly reused in their own code snippet.
new (this) D2; // reuses storage — ends the lifetime of *this f(); // undefined behavior** ... = this; // OK, this points to valid memory
Correct. Because the stoarge has been reused, the "Otherwise" clause applies:
... Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.deallocation]), and using the pointer as if the pointer were of type void*, is well-defined.
Calling f()
through a void*
is not possible, so it is not allowed by that clause. Otherwise, calling member functions of an object whose lifetime has ended is undefined (outside of the destructor).
... = this;
on the other hand is something that can be done with a void*
.
Note that (new (this) D2)->f()
would be well-defined.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With