Yes, it’s cool (for objects created with new
)
For the purposes of this answer, I am using the same draft used in that reply. It’s a rather old draft, but the question is about c++11, after all.
The expression delete this
is legal?
To evaluate the legality of the expression delete this
, first we must know what exactly is the expression this
.
[class.this] / 1
In the body of a non-static (9.3) Member Function, the keyword this
is a prvalue Expression Whose value is the address of the Object for which the Function is called. The type of this
in a Member Function of a class X
is X*
. If the Member Function is declared const
, the type of this
is const X*
, if the Member Function is declared volatile
, the type of this
is volatile X*
, and if the Member Function is declared const volatile
, the type of this
is const volatile X*
.
(My emphasis)
That is, for a class T
, and assuming a member function without specifiers const
or volatile
, this
is an expression with type T*
and value equal to the address of the object in which the function was called.
So, we should know which expressions are valid as operator operands delete
.
[expr.delete] / 2
If the operand has a class type, the operand is converted to a Pointer type by Calling the above-mentioned Conversion Function, and the converted operand is used in place of the original operand for the remainder of this Section. In the first Alternative (delete Object), the value of the operand of delete may be a null Pointer value, a Pointer to a non-array Object created by a Previous new-expression
, or a Pointer to a subobject (1.8) Representing a base class of such an Object (Clause 10). If not, the behavior is Undefined. In the Second Alternative (delete array), the value of the operand of delete
may be a null Pointer value or a Pointer value that resulted from a Previous array new-expression
. If not, the behavior is Undefined.
(My emphasis)
We know that for a guy X
, this
has kind X*
. That is to say, this
is a pointer to an object, and that object is not an array. This pointer is valid as an expression operand delete
if, and only if, the object in question was created with new
. Otherwise, the behavior is undefined.
Assuming then that our object was created with new
, And using the above passages, we have already established that, by itself, the expression delete this
is legal.
If delete this
is legal, what happens to the function running?
To establish what happens then, we must know exactly what the expression delete obj
ago.
[expr.delete] / 7
If the value of the operand of the delete-expression
is not a null Pointer value, the delete-expression
will call a deallocation function
(3.7.4.2). Otherwise, it is unspecified whether the deallocation Function will be called.
and
[basic.stc.Dynamic.deallocation] / 4
If the argument Given to a deallocation Function in the standard library is a Pointer that is not the null Pointer value (4.10), the deallocation Function Shall deallocate the Storage referenced by the Pointer, Rendering invalid
all pointers referring to any part of the deallocated Storage. Indirection through an invalid Pointer value and Passing an invalid Pointer value to a deallocation Function have Undefined behavior. Any other use of an invalid Pointer value has implementation-defined behavior.
(My emphasis)
Note that everything that happens (except any logic inside the destructor) is a memory offset. Yes, the pointer this
is invalidated. It remains to be seen if, when returning, the dereference function this
somehow.
6.6.3 The Return statement
[stmt.Return]
1 A Function Returns to its Caller by the Return statement.
2 A Return statement with neither an Expression nor a braced-init-list can be used only in functions that do not Return a value, that is, a Function with the Return type void
, the constructor (12.1), or the destructor (12.4). A Return statement with an Expression of non-void type can be used only in functions returning a value; the value of the Expression is returned to the Caller of the Function. The value of the Expression is implicitly converted to the Return type of the Function in which it appears. A Return statement can involve the Construction and copy or move of a Temporary Object (12.2). [Note:
A copy or move Operation Associated with a Return statement may be elided or considered as an rvalue for the purpose of Overload Resolution in Selecting a constructor (12.8). - end note] A Return statement with a braced-init-list initializes the Object or Reference to be returned from the Function by copy-list-initialization (8.5.4) from the specified initializer list. [ Example:
std::pair<std::string,int> f(const char* p, int x) {
return {p,x};
}
- end example]
Flowing off the end of a Function is equivalent to a return
with no value; this Results in Undefined behavior in a value-returning Function.
3 A Return statement with an Expression of type void
can be used only in functions with a Return type of cv void
; the Expression is evaluated just before the Function Returns to its Caller.
The pattern doesn’t say anything about pointer dereferencing in function returns. Why would it? Reading your question, I can see that you yourself know that implementations do not need to reference the pointer this
when returning from a member function. All that is required is a return address. Unfortunately the only way to "prove" this statement is by quoting the entire section on return functions. Fortunately, it is not a very large :D section.
If return after run delete this
(And invalidate the pointer this
) does not cause undefined behavior, calling a function that does nothing (only returns) on an invalid pointer is cool?
Yeah, that title could have gotten a little smaller...
This section deals especially with this section of the question:
void MyObj::noop() {
// Não vou tocar no 'this' aqui!
}
MyObj* obj = (MyObj*)0xDEADBEEF;
obj->noop();
This code causes undefined behavior, because we are giving an invalid pointer. Can guess where?
obj->noop()
The above expression has equivalent effect to the expression:
(*obj).noop()
That clearly dereferences the pointer obj
, causing indefinite behavior.
Very interesting question, I will keep an eye to see the answer. Whether it is valid or not I do not know, I just know it performs as expected. :)
– Conrad Clark
Without defining that
MyObj
is or is not polymorphic, it is not possible to say whetherthis
will be accessed in the call of one of your methods. It is not possible to say whethernoop
is virtual because it is not possible to see theMyObj
and whether or not it inherits the method as virtual.– pepper_chico
@pepper_chico, I understand that in practice it will work if it is not polymorphic, but is it really cool in the eyes of the standard? Assume it is not a virtual function if you prefer. Does calling a member function on an invalid object be allowed? It’s that detail that makes the difference.
– Guilherme Bernal
@Guilhermebernal I understand, my point is to reformulate the question so that it is explicit what you want, at the moment only reading the rest of the comments to understand where you are going. Excluding these ambiguities would help the issue itself.
– pepper_chico
IMO, the reference by C. E. Gesser of the pattern seems sufficient to remedy the question if you are pedantic, I say if you follow to the letter what is said, she is covering the case. The pattern says exactly, if you call is UB, as can be seen here, your first case flame, no matter where the execution is at the moment, there will be no call to invalid object methods, in the latter case, yes.
– pepper_chico
@Pepper His answer covers the second case and proves to me that it is in fact invalid. But nothing guarantees me about the case of delete this, as I said in a comment. I will reshape the question as soon as possible.
– Guilherme Bernal
@Guilhermebernal what I tried to say is that guarantees yes, if you follow the paragraph to the letter, at the time of the
delete this
Henceforth there is no call to the method for invalid object, and when there was the last call, it was indeed valid. The paragraph explains about calls on invalid objects and that these are UB, if not, no. That’s why I mean, "regardless of the point of execution".– pepper_chico
What differentiates the point of execution from being inside or outside any function? I find it difficult to refer to such contexts in these cases.
– pepper_chico
@pepper_chico did the editing, it should be a little clearer now my intention. Thank you.
– Guilherme Bernal
@Guilhermebernal Although we know which wheel, the second case should not be discarded? why the excerpt from the pattern by Gesser already implies that this fits into UB.
– pepper_chico
@Pepper If I remove from the question I will make his answer decontextualized and meaningless. I cannot do this.
– Guilherme Bernal
@Guilhermebernal ah ok, I think it’s good to make an Edit that is already assumed as UB so according to the answer, otherwise they will think it is something allowed.
– pepper_chico