📜 ⬆️ ⬇️

10 years of practice. Part 2: Resources

Hello. I planned to write a great article about resource management in C ++.
But in practice, this topic is so complex and multifaceted that I want to dwell on a certain method, which I use myself. This technique is not a salvation for all occasions, but saves a lot of time and nerves when working with objects. However, it is not widely known.

This approach is called "everything belongs somewhere." I first found out about it, transferring from Qt to the wonderful U ++ framework, created by a group of authors headed by Mirek FĂ­dler. It happened about five years ago, so I will share not only the method itself, but also practical advice based on the experience of its use.

In short, the essence of the technique can be described with the phrase: “everything belongs to someone”.
The host class carries all the resources necessary to complete the work. For example:
Objects that describe the bunker nodes are included in the bunker class. Helper objects, as well as data objects for a class of complex calculations, are included in it. And so on.
The exception will be several global variables that represent the most basic objects. In some ways, they are members of a nameless global "class".

Now we say the following:
  1. the object is defined as a normal member of the class, or placed in a container that is a member of the class
  2. ownership of the object is not transferred

Note: it is defined not in the form of a pointer, not in the form of a link, but in the usual way. We can take a pointer to an object, pass it somewhere, but only for use. That is, third-party code cannot delete or new to our pointer. It is managed only by the host class.

')
What if the object is not created immediately?
In this case, you will need either a single container (like unique_ptr) or an array container. Their main function is the automatic deletion of an object in a destructor. Everything!

And here we stop and think about what we got.

1. We got rid of the manual calls to new / delete. And they got rid of it so well that even in the case of emissions of an exception, as well as any other situations, our resources will be guaranteed to be removed.

2. We got a very good automatic resource management system, which takes into account the semantics of the program.
For example, we have several windows. Some of the windows in the course of the program is destroyed. This removes all of its resources. We precisely control the moment of resource removal, it is very important.
We do not rely on the garbage collector. We know for sure that at the right moment the memory will be freed.

Agree, deterministic resource management is a great contrast with the ability to stumble at any time on the garbage disposal procedure (say, while the video is playing, or you need to stop the servo), or the possibility of shaking the garbage collector in a vain attempt to remove the resource at the right time .

3. When the host object issues a pointer to its member, it can guarantee that the pointer of the required class exists under that pointer. When we use such a pointer, we are working in the scope of the host object. So we are guaranteed the validity of the pointer. Do you understand? The lifetime of the object according to the pointer is guaranteed by the compiler itself, which at the compilation stage will check the visibility of the host object.
This is almost a dream: to work effectively through pointers, the validity of which is verified at the compilation stage! And all this - without overhead.

4. Finally, when we realized that we had created, deleting and validity of access issues without overhead, we come to the conclusion that “complex” smart pointers with a reference counter and more complex mechanisms inside, simply become unnecessary.

We impose restrictions on the structure of the program. We carry out more careful design and planning. And due to this, we come to a much more deterministic work with resources without overhead. We abandon all explicit calls to delete, the majority of explicit calls to new, and containers with reference counters — and avoid all the problems associated with them.

Such a program is qualitatively different from the program in C. Here we hide from the scope of all that does not apply to the current functionality. Here, most often, we do not have dozens of global variables or objects and hundreds of global functions that serve them. Instead, there are usually several global object variables. Each of them is sufficiently autonomous and implements a sufficiently large functionality implemented by the included objects. In the last article I mentioned that it is desirable to make these objects private members , as this minimizes the linkage within the class hierarchy.

Here, briefly, what is the approach.

Now about the pitfalls.
First of all, this may mean a completely different style of decomposition into classes, compared to the mainstream approach, which actively uses smart pointers with a reference count. Often, to impose these restrictions is very difficult, and the approach with smart pointers seems more attractive. But, as the practice of the developers of the framework and the practice of the author of the article shows, it is better to try and do everything in accordance with the approach. These efforts will pay off dozens of times with debugging and program support.

Secondly, with active use, you will need good, fast containers that do not impose restrictions on the classes of elements in the form of copy operators. Primitive implementation of such containers will be quite simple. Effective implementation is a matter of a separate article. Because for effective work, you need to implement a transfer (it is also a destructive copy), as well as a transfer (it is also an ultra-fast copy) - if interested, these topics will be discussed further.

Thirdly, the following GUI strategy can follow from these rules. Controls belong to their logical owner (not the window, but the one who stores his semantics in them, because he is its owner). In this sense, you have two choices: MVC apologists can leave everything as it is. Or, you can use this approach, which, as practice shows, often pays for itself. Of course, this is not a sacred cow, but on the contrary - is subject to critical reflection and testing in practice.

Please point out inaccuracies, try, criticize, apply. I will welcome any feedback.

Literature:
1. U ++ overview .

In the next part we are going to discuss the transfer (pick beaviour) and the transfer (moveable) of objects. It will be shown how these opportunities give a significant increase in the speed of work with objects. And containers based on them are 4-5 times faster than STL .
Then you can go to the turbo-speed and very safe implementation of multithreading, inspired by Erlang.

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


All Articles