Resource Acquisition is Initialization, is It Not?

Two Stage Initialization Considered Harmful

For producing any serious C++ code, you do not get around the RAII idiom. It is typically praised for many things. However, the merit of one-stage initialization of objects is not given the attention it deserves.

Resource Acquisition is Initialization

It goes under many names and means many things: resource acquisition is initialization or resource release is finalization, scope-based memory management (SBMM) or lazy initialization. The principle is always the same: we acquire/initialize a resource in the constructor and release/cleanup the resource in the destructor. Here is the template:

// Example 1
class Resource 
{
public:
  Resource(Argument arg)
    : _member(arg) // initialize members 
  { /* further initialization; potentially throws an exception */ }
        
  ~Resource()
  { /* release resource */ }
        
private: 
  Member _member; 
};

Essentially, everything can be considered a resource. Classical examples comprise C-style file handles, memory not held by smart pointers or network connections. The most obvious advantage of RAII is that the resource automatically is released when its owner goes out of scope. In other words, as a developer we do not need to explicitly release the resource. This scope-guarded release of a resource is particularly useful when the scope is left because of an exception.

Single-Step Initialization

We should not overlook another very important advantage of RAII: scope-based resource management allows us to initialize an instance in a single step. If the initialization fails for whatever reason, we can simply throw an exception and the object counts as not constructed at all. You heard right, if you allocate an instance on the heap and the construction fails, you will not have to clean up. However, there is a massive gotcha pertaining to the constructor of a resource we failed to construct:

// Example 2 
class Resource 
{
public:
  Resource(Argument arg)
    : _member(arg) // throws
  {
    try { 
      _member.doDangerousStuff(); /* <= throws */ 
    } catch(...) // catch it 
      { 
        // NOTICE: destructor will not be executed
        _member.undoDangerousStuff(); // we have to cleanup here 
        throw; // rethrow exception
      }
  }
    
  ~Resource()
  {
    try
      { _member.undoDangerousStuff(); /* throws => */ }
    catch(...)
      { /*  be extremely careful with exceptions in the destructor! */ }
  }
        
private: 
  Member _member; 
};

Check out the constructor: if we implicitly acquire a resource in the initializer list and an exception is thrown, then no special precautions are necessary. However, if we explicitly initialize a resource in the constructor body (this could be a simple memory allocation) and an exception is thrown from the constructor’s body, then we can not rely on the destructor and instead we will have to explicitly clean up ourselves. Why is this? Let us look up, how destructors work:

  1. Call the destructor if the respective object has been fully constructed (i.e., the constructor body has been executed successfully).

  2. Call destructors of members variables in the reverse order in which they are listed in the class definition (this is why you get a compiler warning, if you mix up the order of variables in the initializer list).

  3. Recursively apply steps 1+2 for all base classes of the respective class.

Why Two-Step Initialization is Bad

The opposite of single-step initialization is {two,multi}-{stage,step} initialization. This allows for extremely lightweight constructors (maybe just assign initial values). Any real work is executed in an initialize() method. Sounds appealing at first, but two-step initialization has implications.

So your constructor always works out, but initialization may throw. If it does, your object remains in the Uninitialized state. For every method you write, you will have to ask yourself: “What shall we do, if the object is uninitialized?” and you will have to introduce additional conditionals. For each class that initializes and owns a two-step initialized instance, you will have to ask yourself: “What if my member variable is in this zombie state?” The class Resource from Example 2 illustrates the kind of additional precautions which are necessary to maintain such an object.

Additionally, one must not forget that whenever you enforce client code to call initialize()/deinitialize() this is a gotcha. You created a bug that waits happen: at some point in time your colleague will show up and bug you about that code he has written based upon your class and will ask about which artistry you consider necessary to make his code work under which conditions. Single-stage initialization is more restrictive and ‘heritable’: if Resource throws during construction, then any class with a value member variable of type Resource will also throw during construction. The owning class and Resource are less coupled, if you maintain Resource using a smart pointer. Your resource being not there makes perfect sence, if Resource (e.g., a connection) is currently not available. Accordingly, the state of smart_ptr<Resource> is well defined. To everybody inspecting client code, ptr != 0 is much clearer than ptr != 0 && ptr->isInitialized().

For getting a bit closer to completeness on this topic, let us consider a more exotic case: if you want your member variable to be const, two step initialization is not an option.

Limitations

Okay, so far we covered one part of the Truth™. If you are constrained such that you are not able to use exceptions (in case exceptions occur, they are not for free), then you will have a much harder time of getting around two-stage initialization. Also, if your initial state (before initialize()) is a valid state in its own right, there is no reason not to use initialize(). But why not call it changeState() or activateResource() then?

Finally, what if a class requires you to call deinitialize() before (implicitly) destroying an instance and the deinitialize() throws one or more exceptions which can not simply be logged and swallowed. Depending on the situation it becomes increasingly difficult to get around two-step finalization. You could use a RAII wrapper. If it has references to all handlers you need to inform in case of an exception, this can work out. But since you can not use virtual method calls in the wrapper’s destructor, it needs to know every handler class directly rather than as a pure abstract base class.

Conclusion

Single-stage initialization and throwing exceptions from the constructor are valuable tools for keeping unnecessary complexity out of your classes. Use it, where you can. Its evil twins are called two-step initialization and two-step finalization. Avoid them, where you can.

1 Comment

From: Lucas
2016-01-25 13:47
Totally agree. The advantages of RAII cannot be emphasized too much! Easier to write, read, and maintain and waaaay safer (than C can ever be)!

Post a Comment

All comments are held for moderation; Markdown formatting accepted;
By posting you agree to the following privacy policy.

This is a honeypot form. Do not use this form unless you want to get your IP address blacklisted. Use the second form below for comments.
Name: (required)
E-mail: (required, not published)
Website: (optional)
Name: (required)
E-mail: (required, not published)
Website: (optional)