Pseudo-Destructor Call Does Not Destroy an Object

Pseudo-destructor call does not destroy an object

But it is not true, why?

§5.2.4/1:

The only effect is the evaluation of the postfix-expression before the dot or arrow.

Where the postfix-expression is the expression of the object for which the call takes place. Thus a pseudo destructor call, as a call to a trivial destructor, does not end the lifetime of the object it is applied to. For instance,

int i = 0;
(i += 5).~decltype(i)();
std::cout << i;

You can't actually call a destructor for scalars, because they don't have one (see [class.dtor]). The statement is solely allowed for template code in which you call the destructor of an object whose type you don't know - it removes the necessity of writing a specialization for scalar types.


It was noted in the comments that [expr.pseudo] does imply the existence of a destructor for scalars by

The use of a pseudo-destructor-name after a dot . or arrow -> operator
represents the destructor for the non-class type named by type-name.

However, this is inconsistent with other parts of the standard, e.g. §12, which calls a destructor a special member function and mentions that

A destructor is used to destroy objects of its class type.

It appears to be an imprecision created in C++98 days.

What is the effect of call to a trivial destructor?

Starting with C++20 trivial destructor calls end the lifetime of objects. Before that they did not and it was valid to call the destructor multiple times.

In C++17 (draft N4659) trivial destructors are explicitly excluded from ending the lifetime in [basic.life]/1.3 and objects with trivial destructor would live instead until their storage duration ends or their storage is reused ([basic.life]/1.4).

This was changed with the resolution of CWG issue 2256 in this draft commit.

Also note that pseudo-destructor calls also end the lifetime in C++20, but did not before that. Both questions you link in your question are talking about such pseudo-destructor calls. See the compatibility note against C++17 in [diff.cpp17.basic]/1 of the draft (N4861).

C++ Pseudo Destructor on Array Type

The program is ill-formed, you may not use a pseudo destructor call on an array type. §5.2.4 Pseudo destructor call [expr.pseudo]:

  1. The use of a pseudo-destructor-name after a dot . or arrow -> operator represents the destructor for the non-class type denoted by type-name or decltype-specifier. ...
  2. The left-hand side of the dot operator shall be of scalar type. The left-hand side of the arrow operator shall be of pointer to scalar type. ...

An overloaded function can handle the destruction appropriately for both array and non-array types by manually destroying each of the array elements (Live code):

template <typename T>
void destroy(T& t)
{
t.~T();
}

template <typename T, std::size_t N>
void destroy(T (&t)[N])
{
for (auto i = N; i-- > 0;) {
destroy(t[i]);
}
}

template <typename T>
struct Foo
{
typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type store;

template <typename... Args>
Foo(Args&&... args)
{
new (&store) T { std::forward<Args>(args)... };
}

void Release()
{
destroy(reinterpret_cast<T&>(store));
}
};

Explicitly invoking `int` destructor - why is a type alias required?

It works because the grammar didn't make provisions for built-in types, but it did make provisions for aliases:

[expr.post]/1:

postfix-expression:
postfix-expression . pseudo-destructor-name
postfix-expression -> pseudo-destructor-name

pseudo-destructor-name:
~ type-name
~ decltype-specifier

And [dcl.type.simple]/1:

type-name:
class-name
enum-name
typedef-name
simple-template-id

You can imagine what each variable under type-name stands for. For the case at hand [expr.pseudo]/1 specifies that it is just a void expression:

The use of a pseudo-destructor-name after a dot . or arrow -> operator
represents the destructor for the non-class type denoted by type-name
or decltype-specifier. The result shall only be used as the operand
for the function call operator (), and the result of such a call has
type void. The only effect is the evaluation of the postfix-expression
before the dot or arrow.

The interesting thing to note, is that you should be able do that without an alias (if you have a named object), because the pseudo destructor call also works with a decltype specifier:

auto a = int{1};
a.~decltype(a)();

Templated Manual Destructor Call Not Working

To allow templates to deal generally with types, C++ allows a obj.~type() or ptr->~type() syntax even when the type is not a class type (but not for an array type). The meaning is the same as whatever would happen to an automatic object of that type at the end of its scope: if it is a class type, the destructor is called, and if not, nothing happens. For the case when the type is not a class type, this syntax is called a pseudo-destructor.

Now looking at your example, you're using the class template specialization TestClass<MyClass*>. So in the instantiation of the member definition for TestClass<type>::Replace(), type is an alias for MyClass*. The statement

type *pointer = reinterpret_cast<type *>(&data[0]);

defines a variable of type type*, which is MyClass**. (The right-hand side is confusing: &data[0] is the same as data assuming it points at something, and both expressions already have type type*.)

The statement

pointer->~type();

says to destroy the object of type type which pointer points at. That is, it says to destroy the object of type MyClass* which is *pointer. MyClass* is not a class type; it is a pointer type. So this is a call to a pseudo-destructor, and absolutely nothing happens.

There's not enough context to say for certain how this would be fixed, but perhaps you need to use TestClass<MyClass> instead of TestClass<MyClass*>? (Also, in real code don't forget the Rule Of Five/Rule Of Three.)

C++ - Running destructor on a primitive?

Destructors are part of class definitions. If by "primitive type" you mean fundamental types, then the question doesn't make sense, since fundamental types aren't class types, and there's nothing that can "run".

There's a grammatical construction called a pseudo-destructor, but it isn't a destructor and doesn't run.



Related Topics



Leave a reply



Submit