-1

The syntax of the destructor is ~classname. This results in the need to explicitly write the type of object in the destructor call. To avoid this, C++17 introduced std::destroy_at.

So, what was Bjarne Stroustrup's original rationale for choosing the ~classname syntax for the destructor? If the syntax does not depend on the types, std::destroy_at is not needed.

  • "If not, std::destroy_at is not needed." ? If not what? There was a rationale to use `~classname`, what do you mean with "if not?" – 463035818_is_not_an_ai Sep 12 '22 at 09:07
  • 8
    Destructors predate templates by a comfortable margin. Back when Stroustrup came up with destructors, the very *point* of them was that you wouldn't have to call them explicitly, like, ever. You still very rarely do. Which is why `std::destroy_at` didn't make an appearance earlier. – DevSolar Sep 12 '22 at 09:10
  • @DevSolar: if you don't call destructors explicitly, how to you prevent memory leaks? – Dominique Sep 12 '22 at 09:18
  • 7
    Bitwise negation is `~` and turns every bit to its complement. The complement of initialization (the constructor) is deinitialization (the destructor). Hence the cute use of `~`. – StoryTeller - Unslander Monica Sep 12 '22 at 09:22
  • @Dominique In the vast majority of cases, destructors are called for you automatically. You need to call them explicitly only rarely (such as when you want to implement `std::vector` or `std::optional`). – Daniel Langr Sep 12 '22 at 09:23
  • @Dominique Destroying an object is not the same as releasing memory. The only time you need explicit destruction is when you want to end an object's lifetime *without* releasing its memory. (Which is almost never.) – molbdnilo Sep 12 '22 at 09:28
  • hm well, the edit changed the question a bit. " If the syntax does not depend on the types" makes a reasonable point. I guess there are weird cases inolving inheritance where you'd want to call a specific destructor, where the call to the destructor **must** include the information on what destructor to be called somehow – 463035818_is_not_an_ai Sep 12 '22 at 09:40
  • 4
    @Dominique: If you find the need for calling a destructor manually, chances are excellent your code is poorly designed. Manual destructor calls, manual memory allocation (outside of `make_shared` / `make_unique`) etc. are code smells. This usually happens because someone came from Java, C# etc. and didn't take the time to understand how idiomatic C++ handles objects, object lifetime, and resource management. – DevSolar Sep 12 '22 at 09:41
  • @DevSolar calling a destructor vs smart pointers is the wrong comparison imho. Usually managing a dynamically allocated object manually does not involve calling a destructor explicitly either. – 463035818_is_not_an_ai Sep 12 '22 at 09:43
  • @463035818_is_not_a_number That was not intended as a comparison, more like (the beginning of) a listing of "things you should only do *if you know what you are doing, and why you aren't doing it the way it usually should be done*". ;-) – DevSolar Sep 12 '22 at 10:18
  • @463035818_is_not_a_number I do not understand why someone would want to destroy only the base class subobject of the object. – Blackteahamburger Sep 12 '22 at 10:23
  • You may be interested in the book **The Design & Evolution of C++** by Bjarne Stroustrup (1994) which explains the rationale for the early language design decisions. It's an older book, but still relevant for the language during its formative years. – Eljay Sep 12 '22 at 12:41

2 Answers2

9

First of all, I am not sure about the initial rationale for ~ in ~T(). The thing is that most of the time it does not matter how the destructor is called manually.

Usually you never call a destructor explicitly. Most of your variables should use automatic storage:

{
    foo x;
}

The destructor of x is called automatically when x goes out of scope. Explicitly calling the destructor of x is wrong, because once it goes out of scope it would be called again resulting in undefined behavior.

Sometimes you need dynamic allocation (do it via smart pointers, but for the point here manual is "ok"):

foo* p = new foo();
delete p;

You must delete what you created via new. Also here calling the destructor explicitly would be wrong, because delete calls the destructor and frees the allocated memory. Only calling the destructor will not free the memory and calling the destructor twice is undefined behavior.

The only case you have to call a destructor explicitly is when you allocate memory and create the object in two seperate steps via placement new: What uses are there for "placement new"?.

Placement new isn't something you use everyday. It is mainly found in libraries. It is a case where you need to call a destructor explicitly and that is the case when std::destroy_at can be handy.


Suppose you write this code:

void foo() {
    auto x = returns_some_pointer();
    // ....
    ... now delete *x ...
}

Suppose returns_some_pointer returns a pointer to some object. Further suppose that object has been created via placement new and at the end of foo we want to call its destructor. foo doesn't need to know the type of x for anything. To call the destructor it would need to know T to be able to call ~T(). The type can be deduced from x itself, but thats not very convenient. A very convenient way to deduce T is to use a function template, and that is destroy_at(x);.


TL;DR: For most cases it does not really matter how the destructor is called, because it will be either called automatically or under the hood by calling delete. std::destroy_at is not a replacement for the destructor, but rather a convenience method to deduce the type of the object whose destructor is to be called.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Perhaps mention that a function returning an owning pointer (like `returns_some_pointer()` in your example) would be *very* poor design in the general case. (Should return a smart pointer at the very least.) – DevSolar Sep 12 '22 at 09:40
  • @DevSolar is it actually called "owning pointer" when it points to a placement newed object? – 463035818_is_not_an_ai Sep 12 '22 at 09:41
  • `returns_some_pointer()` created it, and then passed it back to the caller assuming that the caller will take proper care of it. Now the caller "owns" the object and the responsibility. – DevSolar Sep 12 '22 at 09:46
  • @DevSolar Note that in this case, the returned smart pointer would need to use a custom deleter that only calls the destructor. – Daniel Langr Sep 12 '22 at 09:57
  • Not related to the question. I know when to explicitly call the destructor. – Blackteahamburger Sep 12 '22 at 10:25
  • @Blackteahamburger "Not related to the quesiton" is unnecessary exaggeration. The fact that you almost never call the destructor explicitly is of course related to how you call it explicitly. One way to read your question is that it is either purely opinion based or only Bjarne can answer it. I tried to read it a different way. – 463035818_is_not_an_ai Sep 12 '22 at 10:43
  • @Blackteahamburger That's funny because I could not come up ad-hoc with a good example for when you'd want that. ;-) – DevSolar Sep 12 '22 at 11:52
5

Constructors and destructors were a part of C++ from the very earliest days. (They predated the name "C++.") They predated templates as well as the standard container types like vector that templates made possible. In those early days there was little or no allowance for manual construction/destruction as a separate operation from new and delete (e.g. there was no placement-new). Nested classes were also not supported.

All in all, there was no question of ever needing to refer to the destructor except as part of its definition, and no question that there would be any doubt or complexity in forming the destructor's identifier.

Naming the constructor after the class fits with C's general tendency for declarations to resemble the usage of the things they declare (e.g. int *a(int b) results in something where *a(b) is well-formed). The destructor is conceptually linked to the constructor, so it would be weird for the class name to show up in the constructor but not the destructor. And with no support for operator overloading (at the time) beyond operator=, there was no question of confusing ~classname with an overloaded operator~. So it was a simple, low-stakes, mildly witty syntax detail.

Sneftel
  • 40,271
  • 12
  • 71
  • 104