Effective C++, 2E | Item 47: Ensure that non-local static objects are initialized before they're used Back to Item 46: Prefer compile-time and link-time errors to runtime errors. Continue to Item 48: Pay attention to compiler warnings. Item 47: Ensure that non-local static objects are initialized before they're used. You're an adult now, so you don't need me to tell you it's foolhardy to use an object before it's been initialized. In fact, the whole notion may strike you as absurd; constructors make sure objects are initialized when they're created, n'est-ce pas? Well, yes and no. Within a particular translation unit (i.e., source file), everything works fine, but things get trickier when the initialization of an object in one translation unit depends on the value of another object in a different translation unit and that second object itself requires initialization. For example, suppose you've authored a library offering an abstraction of a file system, possibly including such capabilities as making files on the Internet look like they're local. Since your library makes the world look like a single file system, you might create a special object, theFileSystem, within your library's namespace (see Item 28) for clients to use whenever they need to interact with the file system abstraction your library provides: class FileSystem { ... }; // this class is in your // library FileSystem theFileSystem; // this is the object // with which library // clients interact Because theFileSystem represents something complicated, it's no surprise that its construction is both nontrivial and essential; use of theFileSystem before it had been constructed would yield very undefined behavior. (However, consult Item M17 for ideas on how the effective initialization of objects like theFileSystem can safely be delayed.) Now suppose some client of your library creates a class for directories in a file system. Naturally, their class uses theFileSystem: class Directory { // created by library client public: Directory(); ... }; Directory::Directory() { create a Directory object by invoking member functions on theFileSystem; } Further suppose this client decides to create a distinguished global Directory object for temporary files: Directory tempDir; // directory for temporary // files Now the problem of initialization order becomes apparent: unless theFileSystem is initialized before tempDir, tempDir's constructor will attempt to use theFileSystem before it's been initialized. But theFileSystem and tempDir were created by different people at different times in different files. How can you be sure that theFileSystem will be created before tempDir? This kind of question arises anytime you have non-local static objects that are defined in different translation units and whose correct behavior is dependent on their being initialized in a particular order. Non-local static objects are objects that are defined at global or namespace scope (e.g., theFileSystem and tempDir), declared static in a class, or defined static at file scope. Regrettably, there is no shorthand term for "non-local static objects," so you should accustom yourself to this somewhat awkward phrase. You do not want the behavior of your software to be dependent on the initialization order of non-local static objects in different translation units, because you have no control over that order. Let me repeat that. You have absolutely no control over the order in which non-local static objects in different translation units are initialized. It is reasonable to wonder why this is the case. It is the case because determining the "proper" order in which to initialize non-local static objects is hard. Very hard. Halting-Problem hard. In its most general form with multiple translation units and non-local static objects generated through implicit template instantiations (which may themselves arise via implicit template instantiations) it's not only impossible to determine the right order of initialization, it's typically not even worth looking for special cases where it is possible to determine the right order. In the field of Chaos Theory, there is a principle known as the "Butterfly Effect." This principle asserts that the tiny atmospheric disturbance caused by the beating of a butterfly's wings in one part of the world can lead to profound changes in weather patterns in places far distant. Somewhat more rigorously, it asserts that for some types of systems, minute perturbations in inputs can lead to radical changes in outputs. The development of software systems can exhibit a Butterfly Effect of its own. Some systems are highly sensitive to the particulars of their requirements, and small changes in requirements can significantly affect the ease with which a system can be implemented. For example, Item 29 describes how changing the specification for an implicit conversion from String-to-char* to String-to-const-char* makes it possible to replace a slow or error-prone function with a fast, safe one. The problem of ensuring that non-local static objects are initialized before use is similarly sensitive to the details of what you want to achieve. If, instead of demanding access to non-local static objects, you're willing to settle for access to objects that act like non-local static objects (except for the initialization headaches), the hard problem vanishes. In its stead is left a problem so easy to solve, it's hardly worth calling a problem any longer. The technique sometimes known as the Singleton pattern is simplicity itself. First, you move each non-local static object into its own function, where you declare it static. Next, you have the function return a reference to the object it contains. Clients call the function instead of referring to the object. In other words, you replace non-local static objects with objects that are static inside functions. (See also Item M26.) The basis of this approach is the observation that although C++ says next to nothing about when a non-local static object is initialized, it specifies quite precisely when a static object inside a function (i.e. a local static object) is initialized: it's when the object's definition is first encountered during a call to that function. So if you replace direct accesses to non-local static objects with calls to functions that return references to local static objects inside them, you're guaranteed that the references you get back from the functions will refer to initialized objects. As a bonus, if you never call a function emulating a non-local static object, you never incur the cost of constructing and destructing the object, something that can't be said for true non-local static objects. Here's the technique applied to both theFileSystem and tempDir: class FileSystem { ... }; // same as before FileSystem& theFileSystem() // this function replaces { // the theFileSystem object static FileSystem tfs; // define and initialize // a local static object // (tfs = "the file system") return tfs; // return a reference to it } class Directory { ... }; // same as before Directory::Directory() { same as before, except references to theFileSystem are replaced by references to theFileSystem(); } Directory& tempDir() // this function replaces { // the tempDir object static Directory td; // define/initialize local // static object return td; // return reference to it } Clients of this modified system program exactly as they used to, except they now refer to theFileSystem() and tempDir() instead of theFileSystem and tempDir. That is, they refer only to functions returning references to those objects, never to the objects themselves. The reference-returning functions dictated by this scheme are always simple: define and initialize a local static object on line 1, return it on line 2. That's it. Because they're so simple, you may be tempted to declare them inline. Item 33 explains that late-breaking revisions to the C++ language specification make this a perfectly valid implementation strategy, but it also explains why you'll want to confirm your compilers' conformance with this aspect of the standard before putting it to use. If you try it with a compiler not yet in accord with the relevant parts of the standard, you risk getting multiple copies of both the access function and the static object defined within it. That's enough to make a grown programmer cry. Now, there's no magic going on here. For this technique to be effective, it must be possible to come up with a reasonable initialization order for your objects. If you set things up such that object A must be initialized before object B, and you also make A's initialization dependent on B's having already been initialized, you are going to get in trouble, and frankly, you deserve it. If you steer shy of such pathological situations, however, the scheme described in this Item should serve you quite nicely. Back to Item 46: Prefer compile-time and link-time errors to runtime errors. Continue to Item 48: Pay attention to compiler warnings.