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
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();
|
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();
|
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;
};
|
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;
}
|
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();
}
}
|
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;
}
}
|
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()
|
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