Is it legal to delete this in a member function?

Asked

Viewed 453 times

17

The language delete this serves for an object to commit suicide. For example:

void Recurso::release() {
    --refs;
    if (refs == 0)
        delete this;

    // Aqui o 'this' pode ser um ponteiro inválido,
    // tocar o objeto por qualquer meio seria errado.
}

What happens here is that the function was called into a valid object and the life of the object was closed during the performance of the function. It is permissible and legal for the life of an object to be terminated while a member function is running?

This other case shows a call in an invalid object (in that memory there is no one MyObj within its lifetime).

void MyObj::noop() {
    // Não vou tocar no 'this' aqui!
}

MyObj* obj = (MyObj*)0xDEADBEEF;
obj->noop();

Is this code valid? If not, why delete this would be valid since the result is actually the same?

Another example:

MyObj* obj;

void destroyObj() { delete obj; }
void MyObj::func() { destroyObj(); }

int main() { obj = new MyObj; obj->func(); }

Although there is no delete this, the example is similar in the sense that the object is destroyed within func().

Whether or not it is a polymorphic object, this is allowed?

For any real implementation, this is no problem as long as the object is not accessed after destruction. But there may be a Undefined behaviour hiding out here.

In the eyes of the standard, can? (preferably taste with snippets of it)

I don’t want to get into the merits of whether or not this is good practice. (It’s not!)

  • 2

    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. :)

  • Without defining that MyObj is or is not polymorphic, it is not possible to say whether this will be accessed in the call of one of your methods. It is not possible to say whether noop is virtual because it is not possible to see the MyObj and whether or not it inherits the method as virtual.

  • @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.

  • @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.

  • 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 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.

  • @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".

  • 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 did the editing, it should be a little clearer now my intention. Thank you.

  • @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 If I remove from the question I will make his answer decontextualized and meaningless. I cannot do this.

  • @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.

Show 7 more comments

5 answers

4

According to this draft of standard, Section 9.3.1 (Nonstatic Member functions), item 2:

If a non-static Member Function of a class X is called for an Object that is not of type X, or of a type derived from X, the behavior is undefined.

That is, calling a non-static member function of a class X on a non-class object X (or derivatives) results in undefined behavior. There is no exception for functions that do not access this or any member variable or function.

In the first case, after the delete, this keeps pointing to where there was a valid object, but memory has already been returned to the pool system. Therefore, it is not guaranteed that there is a valid object there. As far as I’m concerned, that would fit with that rule should any member function be called after the delete, even if it was a function that did not access the members.

In the second case, clearly the invalid address does not point to an object of the specified class, so it is not valid object. Falls into the rule of undefined behavior.

But in practice, all this will work because you don’t read anything from invalid addresses.

Inspired/adapted response from this question in English.

  • What causes me doubt is: "If a non-static Member Function of a class X is called...". In the case of delete this, the function was called on a valid object of the right type, but this was invalid during the execution of the function. Think also covers this case?

  • I think I’d have a problem if you called another function after the delete. I edited the answer to clarify the point.

3


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 , 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.

0

  • Depending on the implementation? So it’s not a behavior defined by the standard? The link makes statements (already known) without any basis, which is not what I’m looking for.

  • Guilherme Bernal the link mentioned is a very serious knowledge base and contains valuable recommendations.

  • 1

    I’m not questioning the validity of the link, I agree with everything in there. My point is that there is no evidence of what is being said there and that is my goal with the question. Have a solid reference and originated from the standard itself.

0

It is valid, just as it is valid to call the destructor itself (obj->~Class()), but the programmer has a huge responsibility to know what he is doing, that this will not be used then etc. In your case, using smart Pointer would be much better.

  • Do obj->~Classe() undoubtedly is valid. But do this->~Classe() is it also? I don’t know. The problem can be written as: "I can end the life of an object within a member of it?"

  • Must be something used inside the STL and look there

  • Each stdlib implementation uses a lot of features that are specific to the compiler for which they were designed. They’re not fully portable, so they wouldn’t be a good source in this case.

0

Comparing the two cases, there is a difference that in the second the execution of the code still involves making the call to the member function, and in the first this has already occurred.

Without checking the limitations dictated by the standard, I already see this difference as crucial because, although the second case works for non-corphic class objects, for polymorphic class objects would clearly be UB because to call the method is necessary access to vtable which is a given within a valid object. In the case of when the call of the method has already occurred, and the object will no longer be accessed, whether the object is polytrophic or not, would not interfere.

Another thing related to the first case is that, you ensure that this method will only be called to this of objects of free Storage (a.ka.. heap)? Why delete in pointers to objects of Automatic Storage (a.ka.. stack) would no longer be valid.

Based on this and the comments left in other answers, perhaps a thorough reformulation of the question would be a good suggestion.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.