📜 ⬆️ ⬇️

How to safely destroy the object. And other thoughts

Recently I looked at the vacancies of one famous office , thought about the questions (which, by the way, are the same in all their vacancies). And I decided to write a note on the most interesting (in my opinion) aspect of the very first question. Maybe I will get to others, but for now I propose to think about whether it is necessary to make destructors virtual?

The answer is not so simple, and to lure you under the cut I will say that in the implementation of STL you will find only a few virtual destructors.

What should be the full answer to the question about destructors?
')

The essence of the problem for those who are not very aware



So, I bring, to everyone already tired example, representing the wrong destructor:

 #include <iostream>

 class A {
 public:
     A () {std :: cout << "A ()" << std :: endl;}
     ~ A () {std :: cout << "~ A ()" << std :: endl;} // 6
 };

 class B: public A {
 public:
     B () {std :: cout << "B ()" << std :: endl;}
     ~ B () {std :: cout << "~ B ()" << std :: endl;}
 };

 int main () {
     A * a = new B;  // sixteen
     delete a;  // 17
     return 0;
 }

The result is:

 A ()
 B ()
 ~ A ()

That is, the constructor B in line 16 honestly called both constructors (A and B), and the destructor in line 17 caused only a class A destructor, which is fully consistent with the type of reference passed to it.

Everything worked correctly, but destructor B was not called, which could lead to memory leaks, handles and other useful resources.

How to deal with it?


The generally accepted answer is a virtual destructor



If in line 6 we add the word virtual:

 virtual ~ A () {std :: cout << "~ A ()" << std :: endl;} // 6

then all will be happy

 A ()
 B ()
 ~ B ()
 ~ A ()

It was discussed on Habré many times. There were very good articles. But I would like to discuss not how virtual methods work, but more philosophical questions.


Is it good to make virtual methods public?



Let's remember for whom public methods are created. They define the class interface and are created for those who will use the class .

And why are there virtual methods ? Correct - to customize class behavior. That is, for those who will extend the functionality of the class .

Probably any sane person would say that using a class and developing a class are different tasks. You do not need to mix them. Therefore, NVI (Non-Virtual Interface) approaches, bridge-like behavioral patterns, and other tricks to separate the abstraction and implementation were invented. The usefulness of this separation is no longer in any doubt. We will not describe in detail all the details and options, but only make a conclusion.

Conclusion: making virtual methods public is not very good.

If you are experiencing an urgent need for public virtual methods, this is a signal that your interfaces and their implementations are too tightly connected, which is not good and can go sideways.

Second question:


Is it good to destroy objects polymorphically?



As you know, everything that has been created should be deleted. In order not to get confused, not to be mistaken, not to forget anything and not to miss, you must either automate the process of deleting objects (automatic variables, auto_ptr and many similar tools), or you should delete objects somewhere not too far from the point of their creation.

Say, if you create an object (in a heap) in the constructor of a certain container, then it is appropriate to delete this object in the destructor of the same container. Note that in this case there is no need for polymorphic deletion.

Conclusion: polymorphic deletion is a suspicious thing.

If you are experiencing an urgent need for polymorphic removal - this is an occasion to reflect on the design. In fact, a polymorphic destructor is needed less often. Perhaps you should slightly revise the design, abandon the polymorphic deletion, bring the creation and deletion operations closer together, and thus make the code simpler, more harmonious. Let an object live a polymorphic fate, but creating and deleting are pair operations. An object is created of a completely specific type and it is logical to delete it without forgetting about this type. This is not always possible, but very often.


In addition, virtual methods immediately create some limitations.



We will not dwell on this in detail, but the presence of at least one virtual method creates some additional overhead and imposes restrictions. For example, such objects cannot be used in unions.

Conclusion: before making the first virtual method, you can think for a minute: do we really need it?


So what should be the ideal base class destructor?



Allegorically.

Making destructors non-virtual and public (as in the first example) is the same as driving a roller in the Moscow Ring Road. One is not a deft movement, did not have time to dodge, and now the videos are separate, and you are separate ...

Making the destructor public and virtual is like driving along the Moscow Ring Road on an asphalt-paving roller. You do not face anything, you are like in a tank. You can cut circles without fear for your life. But this is not the fastest way; besides, your movement comes into conflict with the general flow.

Making the destructor secure and non-virtual is like moving along the Moscow Ring Road on an armored foreign car with air conditioning, a personal driver behind the wheel and a glass of champagne in hand. It is not only safe and convenient for you, you also do not create inconvenience for others. Although (note) on your foreign car, you will not be able to drive everywhere where the skating rink would drive.

A protected non-virtual destructor is good for almost everyone. It is not virtual and it does not allow class users to create emergency situations like the one shown in the first example. It does not allow the use of the base class directly, which is also often useful. In short, it protects you from errors and forces you to write better code.

Pavers are a good thing. Sometimes they really need them to work. But this is not the only mode of transport and, moreover, it is often more convenient to use them.

So the answer is (full):

If you write something small and not for long - make the destructors public and virtual and do not think about anything. There have never been cases when virtual destructors have let someone down. It is reliable, and there is nothing wrong with that.

If you create software for years with the prospect of development (improvement, transfer to other platforms), then you should seriously think about protected destructors. A clear separation of abstraction and implementation is a useful thing.


All happy holidays and success in the new year!



Thanks to a well-known company for interesting questions. It would be interesting to know, and what answer did they expect? There is a very small input field for the answer. But I touched only one part of the answer to their question.

upd: there was a response to this article . I probably will not answer anything. I have already been diagnosed there. Author, the author obviously does not recognize. Books for him is not an argument. But in the constructive part of the note there are very correct thoughts about the dangers of inheritance in general. As for the codec factory, which the author writes about at the end, he may hope to read the story and about it, if I am going to write about question 4 of the same set of questions.

upd2: For some reason, there are people who perceive this post as an attack on Yandex. Trust me! In this post there is no hidden meaning and no attacks. I just read their questions, they seemed to me worthy of discussion, I wrote a note. And that's all.

Source: https://habr.com/ru/post/110850/


All Articles