Resource Acquisition Is Initialization (RAII) means that when creating an object—a resource—its initialization is also done.
In C++, this happens in the constructor. It is important to always leave a valid object when exiting the constructor.
This means, in particular, that even an error must not leave an invalid object lying around. If an error occurs during the initialization of the object in the constructor, you must not leave half-created data structures lying around or still blocking resources. Resource can mean many things here, such as a pointer to reserved memory, an open file, or a lock for mutual exclusion of multiple processes.
In case of an error, the constructor should be exited with an exception so that higher levels can also release their resources, such as requested memory. However, the exception must also be carefully triggered: previously requested resources must be released beforehand (or in a separate catch with rethrow) as the current instance does not (normally) regain control after the throw.
One aspect of valid is that the associated destructor must be able to completely remove the object in any case on its own and release all still existing resources. Taken together, this very effectively implements stack-based resource management.
A typical C API often shifts administrative tasks into the program code that uses the API. A file object or a database connection must be opened and closed again with a symmetric call. Handles for fonts, brushes, or audio-video codecs are obtained and explicitly released again. If the releasing call is not made—due either to an unforeseen event or simply forgetting—the associated resources are not released. The file remains open; the database connection is not closed; the handles on brushes and fonts run out.
Correspondingly, in the C world, special return values are often used to indicate an error. As a sign that the client should refrain from using the resource, many functions use specific error codes. For example, mktime() returns a –1 instead of the requested conversion in case of an error. And you must check the return value of malloc() against the null pointer.
Usually, the error case in the check is the rarely expected result. One might almost omit the check because this case “never happens anyway.” Then one can only hope that the code is never used in a safety-critical environment.
And as it is rarely expected, we come to the exception: in the C++ world, exceptions are available for such errors. The return value does not need to be checked because a constructor must leave an object in a valid state. If the constructor exits via an exception, the preallocated memory is released by the compiler. If the exception passes through multiple constructors on the stack (the list of current function calls) upward without being caught by a matching catch, then all previous constructions are undone. Only exactly then, when a constructor has been completely executed, is the object considered valid and fully created.
A small, lean example class that is only supposed to encapsulate a character buffer could look like the following listing.
// https://godbolt.org/z/9eeG575Wz
struct Buffer {
const char *data;
explicit Buffer(unsigned sz): data(new char[sz]) {}
~Buffer() { delete[] data; }
Buffer(const Buffer&) = delete;
Buffer& operator=(const Buffer&) = delete;
};
It should be mentioned that—true to the idea that there are “sixteen ways to stack a cat”—the same task could be better accomplished using unique_ptr<char[]>; this is just a simple example.
In C, this would usually involve a malloc, and (careful or paranoid) programmers would need to check the return value to see if the memory was allocated correctly. If not, the process must be aborted somehow. In C++, new always returns a valid pointer or throws a bad_alloc exception. So there are two possible outcomes from the constructor:
The methods removed with = delete ensure that the buffer is not accidentally copied and then the entire internal data structure is freed multiple times: another reason to use unique_ptr.
Editor’s note: This post has been adapted from a section of the book C++: The Comprehensive Guide by Torsten T. Will. Torsten is a C++ expert who has been fascinated by the language since earning his degree in computer science. In addition to C++, he has worked with numerous other languages over the course of his career, including Python, Java, and Haskell. Since 2004, he has contributed his expertise to c’t, a German magazine for computer technology, where he writes about C++ and Python. In his free time, he enjoys photography.
This post was originally published in 3/2025.