I have this code:
struct data {
void doNothing() {}
};
int main() {
data* ptr = new data();
ptr->~data();
ptr->doNothing();
::operator delete(ptr);
}
Note that doNothing()
is being called after the object has been destroyed but before its memory was deallocated. It looks like "object lifetime" has ended however the pointer still points to proper allocated memory. The member function does not access any member variables.
Would member function call be legal in this case?
Given [class.dtor]:
This fragment from [basic.life]:
stipulates that what you have is undefined behavior. However, there's different language here - "the object no longer exists" versus "the object has ended", and earlier in [basic.life], it's stated that:
On the one hand, you do not have a non-trivial destructor, so [basic.life] suggests that the lifetime of the object isn't ended yet - the storage hasn't be reused or released. On the other hand, [class.dtor] suggests that the object "no longer exists" which certainly sounds like it should be a synonym for "ended", yet is not.
I suppose the "language-lawyer" answer is: it's technically not undefined behavior and seems perfectly legal. The "code quality" answer is: don't do it, it's confusing at best.
The other answers are correct, but leave out one detail:
It is allowed if either the destructor or constructor are trivial. Other answers have clearly explained that if the destructor is trivial, the lifetime of the original object has not ended.
But if the constructor is trivial, then an object exists whenever a memory location of appropriate size and alignment is present. So even with a non-trivial destructor and trivial constructor, a brand-new object exists that you can call members on.
The verbiage that the other answers left out, that immediately precedes the end-of-lifetime rule they quoted, says
An important note on usage of a new object trivially created in the storage of the old destroyed one: due to the trivial construction, no initialization has been performed on the data members and they now all have indeterminate value, so you must set their values (either through initialization, or invocation of an assignment operator that doesn't use the previous value) prior to reading any.
In the OP's situation, the original object still lives, so this caveat does not apply.
Yes, in the case of the code in the OP. Because the destructor is trivial, calling it doesn't end the object's lifetime. [basic.life]/p1:
[class.dtor]/p5:
No, not in the general case. Invoking a non-static member function after the object's lifetime has ended is UB. [basic.life]/p5: