std::auto_ptr
don't even acquire a resource. using
blocks). This list describes prototypical RAII and does not apply to all varieties of RAII.
Example:
void myFun()
{
XyzLib::Mutex mutex;
mutex.lock()
// do things in series ...
}
// mutex.unlock() is automatically called in the destructor of mutex
SetCurrentDirectory
in constructor and reset it in destructor, start and stop a timer, set and reset a cursor (wait - arrow), ... Example:
void myFun()
{
CWaitCursor waitCursor;
performLongOperation();
// ...
}
// MSDN: "The object's constructor automatically causes the wait cursor to be
// displayed. When the object goes out of scope (at the end of the block in
// which the CWaitCursor object is declared), its destructor sets the cursor
// to the previous cursor. In other words, the object performs the necessary
// clean-up automatically."
Example:
void myFun (Database& db)
{
Transaction transaction (db);
transaction.beginWork();
//...
if (something.isWrong())
{
throw runtime_error ("something wrong in myFun()");
}
//...
transaction.commitWork();
}
// transaction.rollbackWork() is called in the destructor of
// transaction if transaction.commitWork() fails or is not
// reached, e.g. due to an exception
Similar tasks can be quite daunting in languages that lack destructors and thus RAII.
Stroustrup explains RAII to a former C++ programmer who pretends to never have heard about RAII during 6 years of programming in C++:
"For example, a container is a systematic way of dealing with objects. Some of the things you can have in a container are pointers to other objects, but the container's constructors and destructor can take care of contained objects. The name of the game here is to get allocation out of the way so you don't see it. If you don't directly allocate something, you don't directly deallocate it, because whoever owns it deals with it. The notion of ownership is central. A container may own its objects because they are stored directly. A container of pointers either owns the pointed-to objects or it doesn't. If it contains 1000 pointers, there's only one decision to make. Does it own them, or doesn't it? That's a 1000 to 1 reduction in complexity. As you apply this technique recursively and in as many places as you can, allocation and deallocation disappear from the surface level of your code."
Based on the above quotation from Stroustrup, the following code implements a RAII factory for dynamic objects. I use the term 'factory' here and not 'container' to avoid confusion with traditional containers, e.g., STL containers, and to emphasize the 'creational aspect'. Clarification: Neither the name 'RAII Factory' nor the following code stem from Bjarne Stroustrup.
Example: Consider the following simple class:
class MyClass
{
public:
MyClass ()
MyClass (int i)
void set (int i);
int get ();
private:
// ...
};
Let's implement a RAII factory that creates objects of type MyClass
:
class MyClassFactory
{
public:
MyClass* create () { return imp.keep (new MyClass; }
MyClass* create (int arg) { return imp.keep (new MyClass (arg)); }
private:
RaiiFactoryImp<MyClass> imp;
};
Nothing fancy. MyClassFactory
consists of a few lines of boilerplate code that forwards arguments to the MyClass
constructor, calls the imp.keep()
function, and returns the created object to the caller.
Common RAII factory code is factored out into an implementation class template called RaiiFactoryImp
. Concrete RAII factories like MyClassFactory
delegate repetitive functions to it.
RaiiFactoryImp
basically has/implements two tasks. It:
keep()
) and ~RaiiFactoryImp()
) template <typename T>
class RaiiFactoryImp
{
public:
~RaiiFactoryImp()
{
while (!container.empty())
{
const T* p = container.back();
container.pop_back();
delete p;
}
}
T* keep (T* p)
{
container.push_back (p);
return p;
}
private:
std::vector<const T*> container;
// non-copyable
RaiiFactoryImp (const RaiiFactoryImp&);
RaiiFactoryImp& operator= (const RaiiFactoryImp&);
};
Using the code: Place the RAII factory in the right scope and create as much dynamic objects as you need. All created objects are destroyed at the end of the scope, i.e., when the RAII factory goes out of scope.
void myFun (int n)
{
MyClassFactory factory;
for (int i = 0; i < n; ++i)
{
MyClass* p = factory.create (i);
// ...
}
}
// all objects created by factory are deleted here!
create()
function forwards arguments to the object's constructor. NULL
ness. The RAII Factory deletes all objects when it goes out of scope. You have to put the factory in the right scope. But what is the right scope?
If you know in advance that you want to create only one object, you don't need to dynamically allocate anything and you also don't need a RAII factory. Just put the object on the stack. Likewise, for a fixed number of objects, you will probably use a stack-based array.
For an unknown number of objects - unknown at compile time - we have to distinguish between value objects and entity objects. See this article for a brief description of the distinction between value objects (Point
, Date
, ...) and entity objects (Account
, DataBaseConnection
, ...).
· // create n value objects
· void myFun (int n)
· {
· vector<Point> container;
· for (int i = 0; i < n; ++i)
· {
· //...
· Point pt (x, y);
· container.push_back (pt); // copy by value
· //...
· }
}
The RAII factory described so far contains the basic implementation that can be extended in various ways. Examples:
The following example outlines an OO variant of a RAII factory that produces base and derived class objects alike (if the constructor parameters match).
// base.h
class MyBase {
public:
MyBase (int i);
virtual ~MyBase(); //virtual destructor!
// ...
};
A class derived from MyBase
:
// derived.h
class MyDerived : public MyBase {
public:
MyDerived (int i);
virtual ~MyDerived();
// ...
};
MyPolymorphicFactory
produces MyBase
objects as well as objects of types derived from MyBase
.
class MyPolymorphicFactory
{
public:
template <typename T>
MyBase* create (int arg) { return imp.keep (new T (arg)); }
private:
RaiiFactoryImp<MyClass> imp;
};
Using the code:
void myFun (int n)
{
MyPolymorphicFactory factory;
for (int i = 0; i < n; ++i)
{
MyBase* m = factory.create<MyBase> (n);
// ...
m = factory.create<MyDerived> (n);
// ...
}
}
Note how the create
function is defined and invoked as factory.create<MyBase>()
or factory.create<MyDerived>()
. The type of the object actually created is specified in angle brackets after the function name. This is called 'explicit function template argument specification'. You need a C++ compiler with advanced template support for this to work (not with VC++ 6.0).
Sometimes (rarely) you need to immediately destroy an object instead of waiting until the RAII factory goes out of scope. For this purpose, a dispose()
function can be implemented. Be sure though that no dangling pointer is left in your code in this case.
MyClass* p = factory.create (i);
// ...
factory.dispose (p); // will delete p
Questions that may arise now:
create()
functions to accomplish continuous ownership of created objects? raii_factory_vector
, raii_factory_list
, raii_factory_map
, etc.? There are some good reasons to separate creation (factory) from iteration and other container functionality. This way you create objects by using a RAII factory and put them in a different, specialized container - preferably in a container appropriate for pointers like ptr_vector or in a hash table for fast access by key. Separation of concerns provides for flexibility in this case.
On the other hand, an all-in-one solution also has its merits. It's convenient to create and use objects in one place. In an example in the source code download, operator[]
is implemented which can be used as iterator through the RAII factory, e.g.:
MyClassFactory factory;
// ...
for (int i = 0; i < factory.size(); ++i)
{
cout << *factory[i] << endl;
}
So, what's the main difference between usual containers and RAII factories? Whereas containers are generic data structures, RAII factories are much more specialized in their objective. Resource management is only their most basic purpose. You easily come up with many useful features that you want to add to a RAII factory.
A powerful application of RAII factories are tree-like structures where each parent element produces n child elements which in turn become parent elements and so on. Many real world domains can naturally and efficiently be modeled as hierarchies of objects that produce and own their descendants.
Maybe you have seen C++ programs with new and delete statements scattered around the code, sometimes decorated with many try/catch blocks. This is a maintenance nightmare ("Shall I delete this object here or is it used further on?") and a resource management 'anti-pattern'. Most horror stories about 'memory problems' in C++ programs stem from people who have tried this approach. Manual management of dynamic objects, resources in general, is impractical for any but very small applications.
'Smart pointers' are not pointers but objects that mimic one aspect of pointers, that is, dereferencing. Additionally, they perform some extra, 'smart' task. E.g., std::auto_ptr
and reference-counted 'smart pointers' manage the lifetime of objects for which a pointer is passed to them. In many cases, this combination of (partial) pointer functionality and resource management produces more troubles than benefits. Furthermore, resource-managing 'smart pointers' thwart deterministic resource management (you can always return, e.g., an auto_ptr
). If you ask me, I would avoid 'smart pointers'. However, 'smart pointer' certainly is the best name invented in the history of C++.
Garbage Collection (GC) is a runtime mechanism that recycles unreferenced memory ('garbage') but offers no guarantee for the controlled destruction of objects. GC defeats the purpose of RAII and the unique advantage of C++: deterministic encapsulated resource management. You neither know when the collector kicks in nor when and if a 'finalizer' is called (and are even advised to avoid using a Finalize method). Exception handling gets complicated in GC languages because the responsibility of releasing resources other than pure memory is shifted on to the user. Strangely enough, GC does not necessarily prevent memory leaks [link1, link2, link3]. For C++, there is hardly a good reason to trade RAII for GC.
After a recap of the basic idea of RAII, this article has presented RAII factories, a special variety of RAII to handle dynamic objects. Compared to C++ code that is unaware of it, RAII saves you most new and delete statements. Compared to GC languages, RAII saves you most try, catch, finally, and using
blocks. RAII fosters a C++ programming style that greatly simplifies resource handling. In Stroustrup's words again: "As you apply this technique recursively and in as many places as you can, allocation and deallocation disappear from the surface level of your code".