Garbage Collection - No Thanks

Contents[Show]

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 wast its time cleaning up the whole garbage. My second point is more sophisticated.

We have RAII in C++ and therefore the totally 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 behaviour 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 it constructor (line 11 - 13) the resource and release the resource in its destructor (line 14 - 16). It does it 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).

The screenshot displays the lifetime of the objects.

raii

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.

  1. You have to explicitly use smart pointers in C++
  2. 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++ adhere 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 modelling 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 correct in both non error and error case.

Under the condition of a 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 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 behaviour of the program: Make users and support happy by replacing crash and data garbage by meaningful messages. An automatism, here destructor or garbage collector, can handle errors only automatically: on minimalistic scale or ignore it completely.

In application programs this should not be acceptable nor should it be.

The famous last words (Bjarne Stroustrup)

BjarneStroustrup

 

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 a screenshot from the abstract and the overview. You have to read the very readable paper by yourself.

paper

 

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 the C++ experts. I write about explicit memory management in C++.

 

 

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 

Tags: memory

Comments   

0 #1 Mark Abraham 2017-01-07 01:58
I think the pattern of doing work in the constructor like OpenFile is already doubtful, compared with passing in the constructed stream (which is also more suitable for testing). It gets worse when by so doing you propagate the problem of throwing clean-up code in a non-explicit way. If client code anyway has to call a close method, let them do so on the stream and keep it simpler by building a component that itself is exception safe.
Quote
0 #2 Zbigniew 2017-07-21 13:58
Your example is a good argument against garbage collector. In other languages this kind of resources requires kind of IDisposable.
Lack of GC force the developer to thing about the design. Example of this could be multithred/agent application which tasks exchange data by pointers. So developer needs to think who will take responsibility of orphaned task, especially if we assume that task can close itself or it can be closed by other. From my perspective this forced design allows better re-use of components.
Quote

Add comment


My Newest E-Books

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 82

All 388900

Currently are 163 guests and no members online