Recently, a note appeared on the simple and efficient "manual" garbage collection in C ++. I fully admit that local garbage collection within a complex class (where there is an active low-level work with pointers) can be justified. But on the scale of a large program, there is a more reliable and simple method of getting rid of memory leaks. Without pretending to the “method for all situations”, I really hope that it will make life easier for at least some of the readers.
The essence of the method is extremely simple: if each object is a variable of any scope or a simple (“stack”) member of another object, then even if the program crashes from an unhandled exception, a correct cleaning will always occur. The task is to reduce all the variety of dynamic scenarios to this scheme.
1. Each object has exactly one owner.
The most important principle. First of all, it means that during the execution of a program an object can be deleted only by a single owner object, and by no one else.
In the simplest, “static” case, this simply means including the object in the owner class in the usual way as a member. I contrast it with more exotic options for including an object in its owner’s class through a pointer or a link (note, not in any class, but in the owner class).
The "root" objects of the program are declared "stack" variables in main (). Moreover, it is better still in main () than in the form of global variables, because in the first case we can guarantee the order of cleaning (opposing the case with a set of global objects scattered across translation units).
By placing all objects in this way, even after throwing out an unhandled exception, the correct sweep will be performed.
class SomeParent { Child1 child1; Child2 child2; }; class Root { public: void Run(); private: SomeParent entry; }; int main(int argc, char **argv, char **envp) { Root().Run();
Of course, this is the most obvious case, not involving any dynamics.
It is more interesting when an object needs to be created in the course of execution:
')
2. Owning containers.
To store a dynamically created object, use a container with auto-clearing. The container itself is declared to be a regular, “stack” member of the class. This could be one of the smart pointer options, or your own container implementation:
template <T> class One { public: One();
In this case, we can say that the container is the owner of the object.
3. Owning arrays.
Used in the case when you need to operate a collection of objects that are combined on any basis. The peculiarity of such an array is clear: in the destructor it correctly destroys all its elements. When added to an array by pointer, the object becomes the property of the array and is also destroyed by it in the destructor.
template <T> class Array { public: T & Add(); T & Add(const T &);
It is clear that the owner of the entire collection of objects is an array, which is a regular, “stack” member of the owner class.
From such owning primitives, you can create fairly complex models. The next level of difficulty is the transfer of objects between owners:
4. Transfer of objects transfers ownership.
Each object has exactly one owner. The owner can transfer the object to another owner, but he loses access to the object.
Transferring an object along with ownership can be done by adding to the arrays and containers a destructive copy of the internal pointer:
template <T> class One { public:
As a result, if the owner returned an array or container from his member function, then he actually transferred the ownership of the child objects to the caller. The caller has become the new owner. And objects have no chance of becoming memory leaks, since they are guaranteed to be cleaned by someone.
Again, I remind you that this all works only in the case when we strictly adhere to the rule that any object has exactly one owner.
This means that even if the owner sends a link or an object pointer “out”, the recipient can ask this object to participate in some functionality (by calling open member functions of the object). But this object cannot be deleted, since it is not its owner:
class CleverEntity { public: void UpdateUI(Window *window)
That's all.
Maybe not a "silver bullet", but for the vast majority of applications, it is quite enough.
More complex scenarios, if desired, can be disassembled in the comments.
PS If you are interested in this topic, I recommend to get acquainted with the library where all these concepts have already been implemented - the Core package (
concept ,
array example ) of the
U ++ framework (BSD license). There, in its own way, this technique is explained, as well as some other interesting features (
fast compilation ,
fast destructive copying ,
acceleration of arrays by an order of magnitude ).
Some theoretical aspects of the approach were outlined in
a previous article.