02 May 2015

Resource Acquisition Is Initialization (RAII)



Introduction


I am a fan of C++. When discussing about programming with my friends, especially RAD developers, I often got questions like how do you manage memory in C++. Is it difficult to handle “new” and “delete”? My quick answer for this kind of question is the famous resource management technique called “Resource acquisition is initialization” (RAII).

This article will try to give an explanation of resource management in C++ and C#.

What is RAII


“Resource acquisition is initialization” (RAII) was developed in 1984 [1]. Although, this technique is old, but have never been obsoleted. New invented language such as RUST [2] shipped with this RAII technique. In the past, some language including Java abandon this technique due to the introducing of garbage collector (GC). Soon later, their brought it back. At first, java only provide try-finally for resolving this behavior. Later, they provided an automatic resource management (ARM) [3-4], a RAII simulator.

RAII is not only for managing memory, it also benefit on any operation that required a pair of initialize and cleanup such as open/close, turn on/ turn off and start/stop. This is very useful, since people tend to forgot cleanup step [5].

Whether you programming in C++1y, C++0x, or just “C with class”, the RAII concept still valid and useful [6-7].



RAII vs GC


When we discussing about resource management, garbage collector (GC) is the concept that should be mentioned. Both GC and RAII free memory automatically.  This article will explain the GC and RAII as the three types of door including manual doors, automatic doors attached with sensor and doors attached with a door closer.

Manual Door

This door represents manual resource management. In Figure 1, this is a simple door.

Figure 1 A manual door


Advantage
  • Simple
Disadvantage
  • Nothing guarantees that the door will not leave open (resource may leak!).


Automatic Door with Sensor

This technique represents GC (Garbage Collector).

This is a high-end door. It is equipped with automatic door sliding system including electronic sensor (usually an infrared curtain) as shown in Figure 2. When the sensor detect that there are something in front of the door, the motor will activate and open the door.


Figure 2 An automatic door attached with sensor

Advantage
  • The door will be closed automatically.
  • No physical touch from user is required which is essential for the users who lost their ability to push the door.
Disadvantage
  • Expensive
  • Required electricity to function.
  • The exact time for the door to close is difficult to determine.  To predict when the door will close, one must understand how the sensor work and how the control system is implemented. Usually, there is some delay added before activating motor.
  • An attempting to manually close the door will break the system.  Although, vender of this product may provide some tune up mechanism to customize how the door operate, but it is still depended on many other factors such as the specification of motor.


Door with Door Closer

This technique represents RAII (Resource Acquisition Is Initialization).

A mechanical device attached to the door as shown in Figure 3.  This device will absorb energy from pushing by transforming it to mechanical potential energy (usually a spring), then release that energy to close the door.
  
Figure 3 A door attached with door closer


Advantage
  • The door will be closed automatically.
  • No electricity required
  • The time when the door is going to close is deterministic. The door starts closing immediately after the users release their hand from the door.
  • It is not recommend, but user still can manually close the door with minor or no damage to the system.
Disadvantage
  • It requires more energy to push the door. User may feel that the door is heavier than a manual door.

Use case of RAII


GC only applies to the memory. Unlike GC, RAII is possible to apply to any situation that you have a risky pair of turn on and turn off operations [8-9]. In C++, RAII is achieved by object’s destructor. In C# or java, the dispose idiom is required.
Suppose that you are a nuclear power plant programmer, and you have to write a code to control a nuclear reactor with the given C API functions below.

 
C++14 msvc-12.0, gcc 4.8.1
void TurnOnNuclearReactor()
void TurnOffNuclearReactorUsingTechniqueA()
void TurnOffNuclearReactorUsingTechniqueB()

Listing 1 The given functions to turn on and turn off nuclear reactor

Listing 1 The given functions to turn on and turn off nuclear reactor
You must achieve the following goal. Otherwise, the nuclear reactor will explode.
  • The pair of TurnOn and TurnOff operation must be called.
  • You will fail if TurnOn had been called and the program exited without calling TurnOff.
  • You will fail if calling TurnOn twice.
  • You will fail if calling TurnOff twice.
  • You can use either call TechniqueA or TeachniqueB to turn off the reactor. But, you code should be extensible for selecting either TechniqueA or TechniqueB.

Manual Resource Management

Consider the code that call TurnOn at the beginning of function and call TurnOff at the end of function as shown in Listing 2.


C++14 msvc-12.0, gcc 4.8.1
  TurnOnNuclearReactor();
  // if IsSomeThingValid is true
  // TurnOffNuclearReactorUsingTechniqueB
  // will never be called
  if (IsSomeThingValid)
  {
    DoSomeThingElse();
    return;
  }
  // Do other operation …
  // If the exception throws, Turn off will not be called
  // No guarantee that this function will be called
  TurnOffNuclearReactorUsingTechniqueB();

Listing 2 A risky implementation

As you notice on code comment, nothing guarantees that the TurnOff operation will be called. To fix this, one must calling TurnOff function in every possible return\throw path as demonstrated in Listing 3.


C++14 msvc-12.0, gcc 4.8.1
  TurnOnNuclearReactor();
  if (IsSomeThingValid)
  {
    DoSomeThingElse();
    // Ensure that it will turn off
    TurnOffNuclearReactorUsingTechniqueB();
    return;
  }
  try
  {
    // Do other operation …
  }
  catch(...)
  {
    // Ensure that it will turn off
    TurnOffNuclearReactorUsingTechniqueB();
  }
  // Ensure that it will turn off
  if (ReactorStillActive)
      TurnOffNuclearReactorUsingTechniqueB();

Listing 3 Fix risky implementation

The problem is fixed, but it is highly overhead and likely to make errors.

The RAII way

Now let apply the RAII technique by first creating a NuclearPowerPlant class.


C++14 msvc-12.0, gcc 4.8.1
class NuclearPowerPlant
{
  using Action = std::function<void()>;
public:
  NuclearPowerPlant(Action&& Enter, Action&& Exit)
    : mExit(std::move(Exit))
  {
    Enter();
  }
  ~NuclearPowerPlant()
  {
    mExit();
  }
  NuclearPowerPlant(const NuclearPowerPlant&) = delete;
private :
  Action mExit;
};

Listing 4 A RAII class

In Listing 4, The RAII object accepts Enter and Close actions, and store the Exit action to the object’s member. The Exit action will be activated when this object is destructing.

By declaring, a local variable is enough to simulate the RAII as shown in Listing 5 but, I use unique_ptr for the benefit of moving.

Listing 6 demonstrate how to use the RAII object. The RAII object was initialized as local variable. When this variable lifetime is end (exit the scope either by return or throw), the Exit action will be called automatically.

I am aware that the exception might still be thrown on calling mExit() and may cause program termination [10].  Since the mExit() bound to C API function, not exception awareness operations, this example could be OK.


C++14 msvc-12.0, gcc 4.8.1
  NuclearPowerPlant NuclearPowerPlantRaii(
    /*Enter*/ [](){TurnOnNuclearReactor();},
    /*Exit */ [](){TurnOffNuclearReactorUsingTechniqueB();}
  );

Listing 5 RAII stack Local variable

C++14 msvc-12.0, gcc 4.8.1
 
  auto NuclearPowerPlantRaii = std::make_unique<NuclearPowerPlant>(
    /*Enter*/ [](){TurnOnNuclearReactor();},
    /*Exit */ [](){TurnOffNuclearReactorUsingTechniqueB();}
  );
  // Do other operation
  // If the exception throw,
  // NuclearPowerPlantRaii’s ~NuclearPowerPlant() will be call 
  // Which will eventually call
  // TurnOffNuclearReactorUsingTechniqueB
  if (IsSomeThingValid)
  {
    DoSomeThingElse();
    // By hitting return,
    // TurnOffNuclearReactorUsingTechniqueB will also be called
    return;
  }

Listing 6 RAII with unique_ptr


Unlike C++, C# object’s lifetime is managed by GC. The C# finalizes is not deterministic. To achieve the RAII, one must use Dispose idiom,
The code in Listing 7 and Listing 8 are the C# version of RAII


C# 5.0 Visual Studio 2013
  class NeclearPowerPlant : IDisposable
  {
    private Action _Exit;
    public NeclearPowerPlant(Action Enter, Action Exit)
    {
      _Exit = Exit;
      Enter();
    }
    public void Dispose()
    {
      _Exit();
    }
  }

Listing 7 RAII class in C#

C# 5.0 Visual Studio 2013
  using (  var NuclearPowerPlantRaii = new NeclearPowerPlant(
  Enter : () => {Driver.TurnOnNuclearReactor();},
  Exit  : () => {Driver.TurnOffNuclearReactorUsingTechniqueB();}))
  {
    // Do other operation
    // If Exception throws, the Dispose() will be called
    // Exit by guard clause will also call Dispose()
    if (IsSomeThingValid)
    {
       DoSomeThingElse();
       return;
    }
   
  }

Listing 8 Using RAII class in C#
Note that if exception throws inside using block, which is equivalent to finally block, it will not possible to detect that exception information [11]. So, one must ensure that all important exception has been caught inside using{} block.



More RAII features on C++


Unfortunately, C# cannot create object’s member RAII. In C++, The life time of RAII member object is determined by the class that stores it. In other word, if the object was destruct, the object member will be destructed too.

Listing 9 shows the class that store RAII object as a unique pointer. When the unique pointer is destructing, it will delete the NuclearPowerPlant object which finally calls the NuclearPowerPlant’s destructor.


C++14 msvc-12.0, gcc 4.8.1
class Military
{
  std::unique_ptr<NuclearPowerPlant> m_PowerPlant;
public :
  void TakeNuclearPowerPlant(
    std::unique_ptr<NuclearPowerPlant>& PowerPlant  
  {
    m_PowerPlant = std::move(PowerPlant); 
  }
};

Listing 9 Class that own RAII object

Using unique_ptr, the NuclearPowerPlant can move to new owner. For instance, when the state is declared by martial law, the military will take control of the reactor. With movable RAII object, the military will also take care the turning-off of the rector as shown in Listing 10.


C++14 msvc-12.0, gcc 4.8.1
auto NuclearPowerPlantRaii = std::make_unique<NuclearPowerPlant>(
    /*Enter*/ [](){TurnOnNuclearReactor();},
    /*Exit */ [](){TurnOffNuclearReactorUsingTechniqueB();}
);
if (gMartialLaw)
{
  Military aMilitary;
  aMilitary.TakeNuclearPowerPlant(NuclearPowerPlantRaii);
  // After leaving this scope (if statement),
  // the TurnOffNuclearReactorUsingTechniqueB() will be called
}

Listing 10 Moving the ownship of neclear reactor

Another advantage of unique_ptr is that it can do reset by just calling reset method. This will prevent you from calling TurnOn or TurnOff twice as shown in Listing 11. This behavior is called idempotent.


C++14 msvc-12.0, gcc 4.8.1
 
// This will turn off and turn on reactor with new turnoff technique.
NuclearPowerPlantRaii.reset(new NuclearPowerPlant(
    [](){TurnOnNuclearReactor();},
    [](){TurnOffNuclearReactorUsingTechniqueA();}));
// This will turn off the reactor (manual way)
NuclearPowerPlantRaii.reset()
// Since the owned object is deleted after last reset
// ,calling reset on at the second time will not call TurnOff twice
NuclearPowerPlantRaii.reset()

Listing 11 Using the reset


Table 1 shows the programming language and their RAII feature.


Language
Simplest way to get RAII
Can object’s member be RAII?
C++
Object’s destructor
Yes
Rust
Smart pointer
Yes
C#
Inheritance with finally semantics (IDisposable interface and “using” keyword)
No
Python
Runtime reflection (__enter__ and __exit__ method with “with” statement)
No
C
Not possible to simulate RAII

Table 1 Programming language and RAII


Summery


  • RAII is lightweight resource management with deterministic behavior. RAII can also apply to non-resource operations that have turn-on/turn-off behavior.
  • GC is high-end resource management and only apply to memory. The object life-time is not deterministic.
  • Most of languages provide local variable RAII, but only few languages allow object member to be an RAII. With object's member RAII, the idempotent behaviors are easy to implement.


Reference



[1] http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization

[2] http://rustbyexample.com/raii.html

[3] http://www.oracle.com/technetwork/articles/java/trywithresources-401775.html

[4] http://stackoverflow.com/questions/477399/does-java-support-raii-deterministic-destruction

[5] ISBN-13: 978-0123750303 Designing with the Mind in Mind: Simple Guide to Understanding User Interface Design Rules, Chapter 8, “AFTER WE ACHIEVE A TASK’S PRIMARY GOAL, WE OFTEN FORGET CLEANUP STEPS”

[6] Does anyone else prefer older c++ code, http://www.reddit.com/r/cpp/comments/yid69/does_anyone_else_prefer_older_c_code_19982003/

[7] How to encourage adoption of modern c++ idiom, http://www.reddit.com/r/cpp/comments/2ddtub/how_to_encourage_adoption_of_modern_c_idioms_at/

[8] http://stackoverflow.com/questions/17261912/why-garbage-collection-when-raii-is-available

[9] http://stackoverflow.com/questions/8712666/when-has-raii-an-advantage-over-gc

[10] More effective C++, Item 11:  Prevent exceptions from leaving destructors, http://bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MEC/MI11_FR.HTM

[11] http://stackoverflow.com/questions/220234/intercepting-an-exception-inside-idisposable-dispose

2 comments:

  1. เขียนเป็นระเบียบเรียบร้อยดีจัง

    ReplyDelete