Global Dependencies Made Easy
Jason Hise, March 28, 2007


Introduction

We've heard before that global variables are a Bad Thing. We've heard that they are a breach of modularization, and that even when encapsulated as singletons they leave a dark mark on your code and tend to attract evil demons. Still, encapsulated as singletons or not, global variables of some form often find their way into large projects. This article is not about the pros or cons of globally accessible objects. It is about making your life easier if you choose to or are forced to use them.


The Ordering Problem

A popular notion is that if you do want to use global objects, they are incredibly hard to get right. The reason that I most often see cited (at least for single threaded applications) is that the lifetimes of global objects are hard to control, and therefore dependencies between globals are dangerous. While mostly accurate when applied to variables defined at file or namespace scope, this theory is not really true when it comes to function local statics.

The big difference between a global variable and a function local static is that the global variable is initialized before main starts executing, and the static variable is not initialized until execution flow passes through its declaration. The lazy initialization provided by the latter gives us a strong degree of control regarding the order in which our objects are created. Now we just need a way to control the order of destruction.


The Big Secret

As it turns out, as soon as we take control of the creation order of global objects, we are already in control of the order of destruction. According to section 3.6.3 of the 1998 C++ Standard:

"Destructors for initialized objects of static storage duration (declared at block scope or at namespace scope) are called as a result of returning from main and as a result of calling exit. These objects are destroyed in the reverse order of the completion of their constructor or of the completion of their dynamic initialization. "

This gives us all the control we need, and forms the foundation for developing a singleton model that supports natural and safe dependencies.


The Meyers Singleton

Let's start with the Meyers Singleton and build from there. For those unfamiliar, this code structure is an incredibly elegant and compact way of encapsulating and providing access to a global object. It is called the Meyers Singleton because it was originally proposed by Scott Meyers.

foo & get_the_foo()
{
     static foo f;
     return f;
}

This is simply a function which contains a local static variable of the desired type and returns it by reference. The instance is only constructed after the function is entered for the first time. This provides the benefit of lazy creation, meaning that the instance is created on demand, and is never initialized if it is never used. If created, the instance will be destroyed upon program completion.


Introducing Dependencies

Now say we have two global objects. One is a simple logger, and the other encapsulates a database connection. We want the database connection to write to the logger both when the connection is being established and when it is being torn down. In accordance with RAII, it is desirable that tear down of the connection be performed automatically in the destructor of the connection object. However, we need to know that the global logger is available at that time and has not already been destroyed.

So long as the connection uses the logger in its constructor, the problem is already solved. If the logger is used before the connection, then according to the standard it will be destroyed after the connection (assuming the connection is used at all). If the connection is used first instead, part of its initialization will cause the logger to be created. Initialization of the logger will still finish before initialization of the connection is complete. This means the logger will always outlive the connection.


A More Generic Approach

Most of the time, if one global object needs to access another during destruction it will require similar access during construction. But this is not always the case, and passively depending on global side effects between two independent functions is asking for trouble. So what would really be nice is a more declarative method to define which objects depend upon others. It turns out this is not too difficult to manage, so long as we adopt a uniform convention for naming the instance functions of our singletons. The first step is to define a templated struct to do the dependency management. The only thing that this type is responsible for is accessing the instance function of the specified type upon construction.

template <typename Type>
struct depends_on
{
     depends_on()
     {
         Type::instance();
     }
};

Next, we jump right into defining the singletons we want. First we implement a very trivial logger, which just provides the required instance function and the ability to write to a file. The copy constructor, assignment operator, and destructor could optionally be provided in the private section to ensure they aren't used by client code.

class logger
{
private:
     ofstream out;
     logger() : out("log.txt") { }

public:
     static logger & instance()
     {
         static logger inst;

         return inst;
     }
 
     void log(const char * msg)
     {

         out << msg << endl;
     }
};

Now we can start work on the connection. All we need to do to declare the dependency on the logger is to add depends_on<logger> as a base class, and it becomes safe to use in the destructor. Check it out:

class connection
    : depends_on<logger>
{
private:
     ~connection()
     {
         // Close connection here.
         logger::instance()
             .log("Connection closed.");
     }
 
public:
     static connection & instance()
     {
         static connection inst;

         return inst;
     }
};

Because base classes are completely initialized before the objects that contain them, this technique gives us the same guarantees that using the logger directly from the constructor of the connection would have. Better yet, the dependency is now self documenting, and saves us having to provide potentially redundant code if multiple connection constructors are available.


Summary

As it turns out, the C++ Standard says a lot more than expected about controlling the order of destruction of global objects. With very little overhead, it can be incredibly easy to write safe, correct, and self documenting code in which some global objects depend upon others. Though there will most likely continue to be debates about the merits of using the singleton pattern, this technique will hopefully make it less painful to manage global objects if or when the need arises.

Articles | Globals