Jun 23 at 7:29
This is an older version of the article. Click here to see the current one.

RAII++ - the powerful implication of always initializing

If you search, most definitions of RAII refer to using “scoped objects” and destructors to avoid leaking resources (e.g.). However, pulling on the literal words Resource Acquisition Is Initialization, yields far stronger implications. Resource acquisition (allocation or ownership transfer) happens at the same time as object initialization. They are tied together so the “resource is guaranteed to be held between when initialization finishes and finalization starts” .

What does this really give us? Sure, we don’t have to remember to free resources, like memory, and they won’t leak. Resource lifetimes and ownership is well defined. But.. RAII implies the resource is always valid. After the object is created and right up until it goes out of scope it’s there. Any time you access the object you know it exists and is valid. Wouldn’t that be nice if you never had to check for a null pointer or check if something succeeded so it’s safe to use? You could spend time writing code for the happy path, knowing execution can’t get there unless all the objects you need exist and are valid.

RAII guarantees that the resource is available to any function that may access the object

I’ll say it again: code for the happy path.

Forget resources. What if we apply this to all objects? What if we start coding so that you can’t create an invalid object?

(TODO: examples)

How?

Lets look at some of the difficulties.

Dependencies

Now, in order to fully initialize an object, all its dependencies must be fully initialized and passed to the constructor. New adopters of this idea may initially find this infuriating. At first it may just seem like a few compiler errors. Then there will appear to be cyclical dependencies that can’t be resolved. You can’t declare a pile of objects and initialize them later at will. At first it seems like you’re just prevented from doing what you know works fine. Some people may reach for shared_ptr. Resist. This is a good thing. This is the compiler telling you the design of your objects needs reworking. How bizarre. The compiler giving errors about code design now? This extended RAII is encouraging good design.. at compile time!

Constructor Failures

What if resource aquisition fails? Exceptions. There’s just no good other way. Some ideas like factory functions that return std::expected or std::optional can work, but IMO it’s fighting the intent of the language. A common complaint of exceptions is performance. Ideas like Herb Sutter’s affordable RAII give me hope.

Object Composition and Returning Values

Going on a bit of a tangent, RAII can be harder when objects are not movable. A stopgap can be to delete the copy and move constructors and assignment operators, then keep all your objects in a unique_ptr. I’d encourage instead supporting move semantics (std::move) and following the rule of 3, 5, zero. Once done, you can construct an object by moving one or more into it, taking ownership of it. You can also return objects, which is often free due to return value optimization.

Temporary Objects in Initializer Lists

There’s a couple of tricks I like to use for this:

  1. Move construction out to an inline make*() call. This can also make the initializer list look a bit neater
  2. Overload the constructor to take the temporary as an argument and call it from another with delegating constructors

Edge Cases

Some objects hold many other objects and the initializer list becomes giant and ugly. Yes, and yes, but right now we don’t have an alternative. Use the above tricks, break up the object and eventually pick between pretty and safe code.

What if you really need delayed initialization but your object has no publically accessible null state? Use std::optional and .emplace(). Ultimately the idea is to have safer and more restrictive code by default. Make your objects non-nullable unless you really need it in a specific case.

Some tools such as lazy evaluation are at odds with always-valid objects. What happens if delayed construction fails? There’s a new failure path and exception safety becomes important.

Don’t throw the baby out with the bathwater