RAII (Resource Acquisition Is Initialization) is a programming technique that was invented by Bjarne Stroustrup, who is the father of C++ language. This technique is mainly used for resource management (allocation and de-allocation), especially automatic de-allocation, and exception-safety.
Its idea is to utilize the life-span of automatic objects, which starts when it is allocated/initialized on the stack and ends when it goes out of scope. The resource is acquired when the object's lift starts and released when the object's life ends. In more detail object's life starts after its constructor is called (here the resource is acquired in the object's constructor) and ends after its destructor is called (here the resource is released).
1. Resource management
The resources in C++ that I can think of include heap memory, file handle, TCP/IP connection, concurrency controls and database connections.
Heap memory:
Use smart pointers to contain the objects/object array allocated in the heap when the smart pointer is initialized. And to manage the release of heap when the smart pointer comes to the end of life.
Reference counting smart pointer:
This type of smart pointer is to enable the allocated object to be shared in the code. It is CopyCosntructable and CopyAssignable. All the smart pointer instances point to the same allocated objects and the value of shared reference-counter is equal to how many smart pointers are alive. Here is the list of this type of smart pointers,
boost::shared_ptr
boost::shared_array
boost::intrusive_ptr
The boost::shared_ptr is not intrusive and its reference counting is sync-ed between threads. So it is thread-safe. Its capability brings the performance penalty as well, usually slower than boost::intrusive_ptr.
The boost::intrusive_ptr is only useful when the source code is accessible. The programmers are required to implement the reference-counting functionality for the class that they want want better resource management on. It is regarded as the cheaper version of boost::shrared_ptr in memory/performance point of view.
The allocated resource is released when the last smart pointer referring to it comes to the end of life.
Non reference counting smart pointer:
If the allocated resource has to be released when the smart pointer comes to end of life. It means that the allocated resource has the same life span as the smart pointer. Here is the list,
boost::scope_ptr
boost::scope_array
This kind of smart pointer is very useful when the allocated objects are needed for temporarily and deleted right after its usage. It does not support ownership transferring
If the allocated resource is required to be transferable, then
std::auto_ptr
std::unique_ptr (C++11)
This can be used in scenarios like,
- returning an allocated objects from functions, which is to transfer the ownership of the allocated object to the caller, who is to be responsible for the release of the allocated objects.
- Transfer the ownership by pass the smart pointer as one augment of the callee-function, who is to be responsible for the release of the allocated objects.
Remember that this type of smart pointer allows only one smarter pointer instance holding this allocated resource. As long as the ownership is transferred, then the old smart pointer points to NULL. Of course use std::unique_ptr if you can, as std::auto_ptr is deprecated.
Also remember that the non-referencing-counting smarter pointer can not be used in std containers. Only reference-counting smart pointer can be used together with std container because some of algorithms for std container involve temporary objects creation, which is to destroy the ownership of non-reference-counting smarter pointer.
Other resources:
File handle, TCP/IP connections, database connections and so on. The same techniques that manage heap memory can be used to manage these resources as well by implementing a user-defined wrapper class either reference-counted or non.
As for the concurrency controls, there are already some library/facility implemented for Mutex and so on. For instance,
*********************************************************************************
void SyncData()
{
static Mutex m;
{
Lock lc(m); // acquire Mutex.
// sync the data
// Lock lc comes out of scope and release Mutex
}
}
*********************************************************************************
2. Exception safety
The wikipedia page about the exception safety[4] has the reasonable good explanation about different levels of exception safety. And Herb and Scott also give better/deep explanation about exception safety in [2] and [3].
For RAII kicking in, it is aiming to achieve the basic exception safety, which is the goal that the programmer should at least achieve. It is to guarantee that there is no resources leakage when exception happens, for instance no memory leakage, no file handle opened, and so on. Here is an example with the resource leaking if exception happens.
*********************************************************************************
void Foo()
{
int* intPtr = new int(0);
// do the actual job
// calling other function that may throw
// or throw exception by itself
delete intPtr;
}
*********************************************************************************
If between new/delete, exception is thrown and causes stack unwinding. Hence intPtr will be goes out of scope and the handle that points to the memory allocated here by calling new operator will be lost. Therefore the memory leakage happens.
However if boost::scope_ptr is used to manage this heap memory in this case, then the smart pointer goes out of scope, its destructor is called and the memory is release. No matter if exception is thrown here and causes stack unwinding, the basic exception safety is always guaranteed.
Please see a few techniques described by Herb Sutter in [2], to achieve strong exception safety. Techniques like copy-and-swap, compare-and-swap and etc.
3. Algorithm guard in scientific computing to guarantee strong exception safety
Here I would like to present one scenario which utilizes the idea of RAII in my daily work to achieve strong exception safety. Here is one piece of pseudo-code of my daily work.
*********************************************************************************
void System::Solve()
{
PreProcess();
algorithm.Solve(); // here may throw
PostPrecess();
}
*********************************************************************************
Often in an algorithm/solver before delegating the work into the actual algorithm/solver, there are some pre-processing work that needs to be done. Also there are some post processing work that need to be done after the solve. The delegating alogrithm.solve() may or may not throw, but we can't stop other programmers to modify it to throw exception in later application development.
The problem arises if alogrithm.solve() does throw, then the 2nd half, PostProcess(), will not be called. This will break the requirement of the strong exception safety. What it means is that the program can hit into a inconsistent state if the exception happens, as PostProcess() is desinged to restore some states made by PreProcess() but not called because of exception.
Here comes the solution by utilizing RAII technique,
*********************************************************************************
class SystemGuard {
public:
SystemGuard(System& sys) : m_sys(sys) {
m_sys.PreProcess();
}
~SystemGuard() {
m_sys.PostProcess();
}
private:
System m_sys;
};
void System::Solve()
{
{
SystemGuard(*this);
algorithm.solve();
}
}
*********************************************************************************
This technique is to utilize the same idea of RAII. To call the PreProcess in the constructor and call PostPrecess() in the destructor. No matter if exception happen or not, this technique will guarantee the 2nd half, PostProcess(), is called to restore the state of program. Therefore the strong exception safety is guaranteed.
Bibliography:
[1] Bjarne Stroustrup, http://www.stroustrup.com/bs_faq2.html#finally
[2] Herb Sutter, "More Exceptional C++"
[3] Scott Meyers, "More Effective C++"
[4] http://en.wikipedia.org/wiki/Exception_safety
No comments:
Post a Comment