Garbage Collection – No Thanks
C++ is old-fashioned. C++ has no garbage collection. No garbage collection? Right! Old fashioned? Wrong!
What is against garbage collection in C++? At first, garbage collection breaks one of the key principles of C++: “Don’t pay for something you don’t use.” That means if you don’t need garbage collection your C++ runtime should not waste its time cleaning up the whole garbage. My second point is more sophisticated.
We have RAII in C++ and therefore the deterministic destruction of objects. But, what is RAII? That’s the topic of this post.
Resource Acquisition Is Initialization
RAII stand for Resource Acquisition Is Initialization. Probably, the most important idiom in C++ says that a resource should be acquired in the constructor of the object and released in the destructor of the object. The key is that the destructor will be automatically called if the object goes out of scope. If this is not totally deterministic? In Java or Python (__del__) you have a destructor but not the guarantee. Therefore, it can end disastrous, if you use the destructor to release a critical resource like a lock. That’s a classical anti-pattern for a deadlock. But in C++, we are on the safe side.
The example shows the totally deterministic behavior of RAII in C++.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
// raii.cpp #include <iostream> #include <new> #include <string> class ResourceGuard{ private: const std::string resource; public: ResourceGuard(const std::string& res):resource(res){ std::cout << "Acquire the " << resource << "." << std::endl; } ~ResourceGuard(){ std::cout << "Release the "<< resource << "." << std::endl; } }; int main(){ std::cout << std::endl; ResourceGuard resGuard1{"memoryBlock1"}; std::cout << "\nBefore local scope" << std::endl; { ResourceGuard resGuard2{"memoryBlock2"}; } std::cout << "After local scope" << std::endl; std::cout << std::endl; std::cout << "\nBefore try-catch block" << std::endl; try{ ResourceGuard resGuard3{"memoryBlock3"}; throw std::bad_alloc(); } catch (std::bad_alloc& e){ std::cout << e.what(); } std::cout << "\nAfter try-catch block" << std::endl; std::cout << std::endl; } |
ResourceGuard is a guard that managed its resource. In this case, the resource is a simple string. ResourceGuard creates in its constructor (line 11 – 13) the resource and releases the resource in its destructor (line 14 – 16). It does its job very reliable.
The destructor of resGuard1 (line 23) will be exactly called at the end of the main function (line 46). The lifetime of resGuard2 (line 27) already ends in line 28. Therefore, the destructor will automatically be executed. Even the throwing of an exception does not alter the reliability of resGuard3 (line 36). Its destructor will be called at the end of the try block (line 35 – 38).
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
The screenshot displays the lifetime of the objects.
I will refer once more to the program raii.cpp. What is the key idea of the RAII idiom? The lifetime of a resource will be bound to the lifetime of a local variable. C++ automatically manages the lifetime of locals.
The idea is quite simple but has far-reaching consequences. Critical resources are bound to local objects. The remaining job is done by the C++ runtime.
RAII everywhere
RAII everywhere holds for locks std::lock_guard, std::unique_lock, and std::shared_lock that manage their resource mutex. RAII everywhere holds for the smart pointers std::unique_ptr, std::shared_ptr, and std::weak_ptr that manage their resource memory.
Thanks to RAII we have in C++ a kind of garbage collection.
But there is a subtle difference to a general garbage collection.
- You have to explicitly use smart pointers in C++
- The memory management with std::unique_ptr has by design no overhead in performance or memory compared to a raw pointer (see Memory and Performance Overhead of Smart Pointers).
Therefore, C++ adheres to its key principle in a twofold way: Don’t pay for something you don’t use.
Special resources
Thanks to RAII, the destructor of the local object and therefore the cleaning up of the resource will be done totally deterministic. So far, so good. If the destructor can throw an exception, the destructor of the object modeling RAII will trigger the exception. This will be the case if the resource is a file. The close function can trigger an exception. Therefore, you have to answer the question for yourself, if it’s ok, that the destructor can throw an exception. If not, you should not use RAII.
Dealing with throwing destructors (Udo Steinbach)
Udo Steinbach wrote an e-mail to me about the issue with potentially throwing destructors. Here is the e-mail.
RAII is a nice thing − as long as no error can occur while destroying the object. The latter is forgotten when talking about RAII. Why a destructor should not throw, you can read in many places: https://www.qwant.com/?q=should%20destructors%20throw. As a result, RAII has to be supplemented manually in many cases and so it seems to be done twice.
class MyFileHandle { public: MyFileHandle(...) :handle(::OpenFile(...)) { if (handle == nullptr) throw ...; } ~MyFileHandle() noexcept { ::CloseFile(handle); } private: MySystemHandle handle; }; { MyFileHandle file(...); ... }
Does CloseFile() not close, the call looks correct but the handle is lost, the user has to restart the program and search and delete the file himself, or other embarrassing symptoms as they are familiar from programs.
So the class must have a throwing
void Close();
and the destructor has to check:
{
MyFileHandle file(...)
...
file.Close();
}
This doesn’t look like pure RAII. For symmetry we need a manual Open():
{
MyFileHandle file;
file.Open(...);
...
file.Close();
}
− RAII is lost. For the lover remains the consolation, that the object is now reusable and runs correctly in both non-error and error cases.
Under the condition of proper error handling from the perspective of the program user, I renounce RAII in many of my classes. Automatic tests according to an idea from boost show
-
exception safety as a minimum,
-
with best-known error handling, which must be dispensed with at RAII, e.g. automatically delete incomplete files and rethrow the exception,
- and messages presentable to the user,
an always best possible behavior of the program: Make users and support happy by replacing crash and data garbage with meaningful messages. An automatism, here destructor or garbage collector, can handle errors only automatically: on a minimalistic scale or ignore it completely.
In application programs this should not be accepted nor should it be.
The famous last words (Bjarne Stroustrup)
Bjarne Stroustrup made a short remark to my news about this post on C++Enthusiasts:
“Things are still improving”: http://www.stroustrup.com/resource-model.pdf
So what is this 20 pages paper about, written by Bjarne Stroustrup, Herb Sutter, and Gabriel Dos Reis. I provide you with a screenshot of the abstract and the overview. You have to read the very readable paper by yourself.
I promise I will write in future posts about the Core C++ Guidelines. But you have to be patient. I first have to catch up with my German blog.
What’s next?
With the next post, I enter the area of C++ experts. I write about explicit memory management in C++.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!