RAII is Overrated

In most discussions of the advantages of C++, an item that is frequently raised is the support for automatic destruction. This mechanism, which is used to reclaim resources used by a single object at the end of its scope, is the basis for a programming style popularly known among C++ users as RAII (resource acquisition is initialization).

The main point of this article is to show that, while RAII is a useful idiom in a complex language such as C++, it really doesn’t provide much help in other, simpler languages. Even in C, which is a lower level language, the automatic destruction mechanism doesn’t provide much that couldn’t be supported by the programmer itself, sometimes with less surprises and higher performance.

Is Automatic Destruction Necessary?

The first issue that needs to be answered is if automatic destruction is a necessary feature in general. One of the ways we can answer this questions is looking at the decisions made by the designers of more recent languages. This may not give us a complete answer, but it shows at least an indication of usefulness for a particular feature.

In fact, there are many languages such as Java, Python, Javascript, and Rubi that don’t have an idiom for RAII. Moreover, among the communities of users for these languages there is no feeling that there is a particular need to add such a feature. This includes both older languages such as Smalltalk as well as languages more recent than C++, such as Javascript.

The main reason most languages don’t require a RAII idiom is that they don’t need to manage memory manually. Memory is the single most common resource guarded by C++ classes, a resource that needs to be released every time an object is discarded.

Another reason for having automatic destruction in C++ is that is provides a decentralized way to manage resources for any number of classes. Compared to C, a C++ program needs to define a lot more user defined data types (classes) in order to do its work. The interaction between features such as the STL and the inheritance mechanism require that a lot of classes be created to represent programmatic concepts — no wonder why C++ programs feel the need to have a mechanism for cleaning up the resulting mess.

Disadvantages of Destructors

One of the problems with automatic destructors in C++ is that they require a lot of run-time overhead. Every object in every class needs to call a destructor, even if it does nothing (which is often the case). It is true that an optimized compiler can eliminate the call to the destructor in some cases. However, when we have virtual destructors this becomes very difficult, since the compiler has no way to know exactly what destructor will be called through the virtual table.

When a system has a large number of objects with virtual destructors, which is a common case on object oriented systems, the result is that destructors are called all the time for no good reason.

A more subtle problem with the RAII idiom is that, while it solves some of the issues of resource management, it doesn’t address the most difficult cases. The only instance where RAII works well is when objects are allocated in the stack. For these allocations, destructors are guaranteed to be called at the end of the scope, even if an exception happens before the end of the scope is reached.

However, C++ has two more memory allocation models: static-allocated and heap-allocated memory. Static memory can be a problem for the implementation of the singleton pattern. It is well known that objects created on static memory will not be destructed correctly. Even when they may be destructed, there are no guarantees. Therefore, static allocation of objects should be dealt with special care, and destructors cannot be of much help in this case.

The other, much more important case of memory allocation is heap-based allocation. It is important because in an object oriented language the objects use have distinct life times. Therefore, one should be able to allocate them and pass references around until they are not needed anymore. The standard way of doing this is allocating memory on the heap and returning that memory to the pool only when the object is done.

Now, the problem with heap allocated memory is that destructors will be of no help in that case. They will not be called automatically when an object is not needed. If a programmer doesn’t take care of cleaning heap-allocated memory, it will just leak when no more references to it exist in memory.

To avoid this, programmers are required to call delete (or delete[]) to make sure that the object is properly cleaned. Thus, programmers are ultimately the ones responsible for this difficult task. If you think of the implementation in terms of C (and other lower level code), in this case the destructor is just a function that needs to be called in the object. There is nothing special in it other then the syntax, which uses the keyword delete.

In fact, the destructor itself is a non-impressive function, with some serious drawbacks. First, it receives no arguments and returns no value. So, for example, there is no way to signal an error condition other than throwing an exception. And we already know that a destructor shouldn’t throw exceptions, which means that it cannot do anything very reliably.

What this amounts to is a system that is far less powerful than necessary to maintain a first class object oriented system.

Conclusion

RAII is preached by some as the most important feature of C++ for resource handling. However, as we have seen above, it has lots of problems that make it much less appealing than initially thought.

For example, destructors work well only when the memory class used by the object is stack based. Other allocation classes will result in memory leaks and in manual work for programmers.

Even the work performed on behalf of stack allocated objects is nothing more than syntactic sugar. While tedious, it is not hard to add a matching clean up procedure at the end of the scope where an object is used, when necessary. If no exceptions are used in code (which is a common case even on C++), this is all one needs to have correct programs.

Similar Posts:

About the Author

Carlos Oliveira holds a PhD in Systems Engineering and Optimization from University of Florida. He works as a software engineer, with more than 10 years of experience in developing high performance, commercial and scientific applications in C++, Java, and Objective-C. His most Recent Book is Practical C++ Financial Programming.

9 Responses to “RAII is Overrated”

  1. I would argue that having the ability to perform some action on scope exit is a very helpful thing to have. Object destruction is one example of that, but RAII seems to have it backwards. It uses the specific example of object destruction to implement the general feature of action-on-scope-exit. The D programming language has a nice construct that goes like this:
    scope(exit) { /* do some stuff */ }
    You can do whatever you want in the “do some stuff” block. You can delete objects to free up memory, release file handles, unset some GPU state that you set when entering the scope, run some code for tracing/profiling, whatever you want. There’s also scope(success) and scope(failure) for specifying code that should run when exiting a scope specific to whether it is a normal exit, or exit via an exception. It also eliminates a lot of the need for ugly try/catch blocks.

    By Bill on Jul 8, 2011

  2. @Bill, I agree that having a mechanism to create scope is a great idea. This is what languages such as common lisp have, so that you can choose how to do resource management. Java provides this only through a try/catch, as in C++, which is not an elegant solution.

    By coliveira on Jul 9, 2011


  3. Now, the problem with heap allocated memory is that destructors will be of no help in that case. They will not be called automatically when an object is not needed. If a programmer doesn’t take care of cleaning heap-allocated memory, it will just leak when no more references to it exist in memory.

    To avoid this, programmers are required to call delete (or delete[]) to make sure that the object is properly cleaned. Thus, programmers are ultimately the ones responsible for this difficult task.

    One nice way around this in C++ which I’ve recently learned is to use STL smart pointers. They are allocated on the stack and they are destroyed when their enclosing scope is exited. So the task becomes easy.

    By Jeannot Langlois on Jul 29, 2011

  4. > In fact, there are many languages such as Java, Python, Javascript, and Rubi that don’t have an idiom for RAII

    Actually, Python has the with statement (http://www.python.org/dev/peps/pep-0343/) to use for automatic destruction in much the same manner as RAII.

    By Muhammad Alkarouri on Aug 5, 2011

  5. Some might argue that manual memory management is a feature. In languages that have a garbage collector, memory is garbage collected at times that are not under control by the programmer. If the application is tight on system resources or has tight run-time constraints, having a garbage collection process run could cause the program to not function properly.

    As such, C and C++ get used for real-time apps. But for lots of apps (business apps, in particular) that don’t need those kinds of constraints, it makes sense to have garbage collection as it reduces the load on the programmer.

    By Jon on Aug 5, 2011

  6. 1. destruction (and RAII) is also used for releasing file descriptors/handles, sockets, mutexes etc. – not just memory. In fact C# has “using” keyword for these non-memory cases.

    2. Virtual destructors are overhead in almost the same situations when a polymorphic destruction in C would have the overhead. (Though C programmer may know that no child class needs special cleanup – but in such case C++ code can omit the virtual keyword and drop the overhead too.)

    3. “It is well known that objects created on static memory will not be destructed correctly.” – Never heard of that. Static objects are destroyed upon process exist in reverse order of their creation. The order of construction is a problem, though.

    4. RAII does not solve the heap allocation – so what? It makes the stack situation easier and the heap situation is obvious, so no harm done there.

    5. Destructors are not allowed to throw, so what? If the class is used in situation where there might be a problem and the problem could reasonably handled by a caller, then provide a Close() method. Destructors are expected to just do a simple cleanup.

    To me, RAII is a great syntactic sugar.

    By Petr on Sep 6, 2011

  7. I’m not sure your conclusion is correct, for a couple of reasons. RAII works for dynamic memory just as well as static through smart pointer classes (see scoped_ptr and shared_ptr from boost, for example, later rolled into the STL). In a modern C++ program, if you’re calling delete you’re almost certainly doing the wrong thing. So that’s the first part.

    The second part is that assuming programmers will add a matching cleanup function and call it correctly every time is going to introduce bugs. We aren’t perfect, we make mistakes. That’s before you throw in exceptions, which foul things up further and require horrific nested try/finally blocks to match the safety of C++. Exceptions exist, even if we’d maybe prefer that they didn’t. I’m not sure I see why we’d choose a more difficult, less safe solution.

    Garbage collection of memory in managed languages only solves a subset of the resource problem, and admittedly it does that pretty well, avoiding the object cycle problem with reference counting (though you can still leak memory). Unfortunately for the rest of the resource types we manage (like file handles, database connections, etc.) garbage collection just doesn’t work.

    By Eric Lawless on Nov 14, 2011

  8. Seriously, I never understood any of these arguments. People go to college to learn how to program and they come out of college using languages that do everything for you. Next thing you know languages will automatically write all your program for you on verbal commands. I can see how managing dynamically allocated memory would be hard in a 1MB+ application however if you properly test your program along the way it shouldn’t be that difficult to remember to free your memory. A program is just a heap, a stack, and registers. Mostly you just have to worry about the heap and stack. And that comes down to the address of this memory, properly referencing the lengths of arrays to avoid buffer overflows, whether variable A is a pointer to an array or an array of pointers, etc. After doing this years it becomes second nature. Develop good coding habits and test your program throughout it’s development and before you know it you have a tight, highly efficient program without using all that automatic stuff. You’ll be a much better programmer doing it this way.

    By Bob Johnson on Dec 5, 2011

  9. “Even the work performed on behalf of stack allocated objects is nothing more than syntactic sugar. While tedious, it is not hard to add a matching clean up procedure at the end of the scope where an object is used, when necessary. If no exceptions are used in code (which is a common case even on C++), this is all one needs to have correct programs.”

    The basic motivation behind something that you call syntactic sugar is writing less and being more expressive in doing so. You have to see programming as a form of asyncronous communication. You leave message for other people or your self in source code files. These messages can be tedious verbosity (C) or clumsy cluttered subjectives (Java), or it can be expressive poetry (C++ when used right).

    By Andreas Pokorny on Mar 20, 2012

Post a Comment