Explain what exactly frees memory when you call a destructor for an object, because by default it has an empty body.
2 answers
Formally, the destructor has nothing to do with freeing the memory of "its" object. Memory freeing is done by external code, which causes the destructor to be released. Therefore, from this point of view, your question is somewhat meaningless.
Destructor is a special feature. Even if he has an empty body, does not mean at all that “he does nothing”. For example, a destructor always implicitly calls the destructors of the subobjects of a given class, even if it has a seemingly “empty” body.
It is clear that the destructor may be responsible for the explicit release of extraneous resources that are directly owned by your object (including memory). But in this case, the destructor's body will not be empty.
In "traditional" implementations, oddly enough, when the destructor is virtual, the function of releasing the dynamic memory of an object is often implicitly called from within the destructor (and not with an external code). Such a trick is necessary in order to ensure the correct behavior of the overloaded
operator deletefor the class (if any). But these are already internal implementation details.
The third paragraph refers to the following situation.
In C ++, the abstract algorithm for the delete operator is reduced to a sequence of two steps:
- Calling the right object destructor
- Calling the right raw memory release function
operator delete(void *).
Function operator delete(void *) , as is known, can be replaced / reloaded by the user. It is allowed both to replace the global ::operator delete(void *) , and to overload the static function operator delete(void *) in specific classes. In this case, the language specification requires that the selection of a specific operator delete(void *) made as if its search (name lookup) was made from the destructor of the object being deleted.
for example
#include <iostream> struct B { virtual ~B() { std::cout << "B::~B" << std::endl; } void operator delete(void *p) { std::cout << "B::operator delete" << std::endl; ::operator delete(p); } }; struct D : B { virtual ~D() { std::cout << "D::~D" << std::endl; } void operator delete(void *p) { std::cout << "D::operator delete" << std::endl; ::operator delete(p); } }; int main() { B* pb = new B; B* pd = new D; delete pb; delete pd; } in such code, when executing delete pb after executing the destructor, B::~B , B::operator delete should be invoked, and when executing delete pd after executing the D::~D destructor, D::operator delete should be invoked. In other words, although operator delete always a static member of a class, it should actually behave like a virtual (!) Function.
In order to satisfy this requirement of the language, most implementations simply transfer the call of a proper operator delete inside of the destructor. Thus, the required "virtuality" of the operator delete function is achieved free of charge due to the virtuality of the destructor.
At the same time, it is clear that operator delete(void *) needs to be called only for full objects located in dynamic memory, and for other objects it is not necessary (that is, not). To take this into account, the compilers supply the destructor with an implicit boolean parameter that tells the destructor whether to call operator delete . Thus, in the above example, destructors will actually have the following form
B::~B(bool call_delete) // неявный параметр { std::cout << "B::~B" << std::endl; if (call_delete) // неявно B::operator delete(this); // неявно } ~D::D(bool call_delete) // неявный параметр { std::cout << "D::~D" << std::endl; B::~B(false); // неявно if (call_delete) // неявно D::operator delete(this); // неявно } The expressions delete pb and delete pd in such a situation simply turn into virtual calls pb->~B(true) and pd->~B(true) . The first falls into B::~B , the second - into D::~D
The GCC compiler, by the way, in earlier versions implemented this approach exactly as described above - through a hidden Boolean parameter, and in modern versions it implements the same approach through the generation of two separate destructors.
The language standard grants that after the body of the destructor (even empty) the memory will be released. An automaton.
- So how does this “automaton” happen? - user227660 4:14
- oneThis is a personal compiler file. Removed in the order of the class field declaration. - gbg