Global objects, static data members, file scope objects and local variables declared static are variables with static storage duration. A strategy for initialization of objects with static storage duration is needed to avoid the risk of accessing uninitialized objects.
Rec 9.1 Objects with static storage duration should only be declared within the scope of a class, function or anonymous namespace.
Rec 9.2 Document how static objects are initialized.
Rec 1.4 - Rec 1.5 , namespaces
Rec 9.1 Objects with static storage duration should only be declared within the scope of a class, function or anonymous namespace.
Static objects make it possible to access an object inside a function without having to pass along a pointer or reference to it. Many objects can use the same object without each object storing a pointer to the object, which can save space and sometimes make the code less complex.
There are also many disadvantages of having static objects. Any function that has access to a static object could use it, which means that it can be costly and difficult to maintain code with many static objects.
In addition they can complicate multi-threaded applications, since it is necessary to protect static objects so that their states do not become invalid if two threads modify an object at the same time.
We recommend you to limit the scope of a static object to a class, a function or an unnamed namespace. By doing so, it is possible to know in advance where a static object is accessed.
Encapsulate access to static objects as much as possible. If you can declare a static object within a function, you should do that. Such objects are guaranteed to have been initialized before the first use of the function.
The choice between a static data member and a static object within an unnamed namespace is not as obvious. The latter alternative is more flexible regarding scope, but the first choice allow you to put the implementation of a class in many files.
Unnamed namespaces allow you to use the same name for many different objects with static storage duration. For example, it is common to have a static string to identify each implementation file that a program uses.
Old C++-programmers should know that objects within unnamed namespaces replace static objects in file scope. The language has changed, and there is now no guarantee that static objects in file scope will be supported in the future.
int randomValue(int seed) { static int oldValue = seed; // calculate new value return oldValue; }
A singleton class is a class with only one instance. It is common to store a static data member that is a pointer to that object. By doing so static member functions can have access to the object.
The pointer is not local to a function since many static member functions need access to the object.
class EmcSingleton { public: static EmcSingleton* instance(); static void create(int i = 0); // ... private: // private constructors EmcSingleton(int i); // ... static auto_ptr<EmcSingleton> instanceM; }; EmcSingleton* EmcSingleton::instanceM = 0; void EmcSingleton::create(int i) { instanceM = new EmcSingleton(i); } EmcSingleton* EmcSingleton::instance() { if (! instanceM) create(); return instanceM; }
// myfile.cc namespace { // sccsid is not visible to other files const char sccsid[] = "@(#)myfile.cc ..."; } // ...
// Not recommended if your compiler allows you to // have unnamed namespaces static const char sccsid[] = "@(#)myfile.cc ...";
Rec 9.2 Document how static objects are initialized.
Static objects defined in different implementation files are initialized in an order that is not specified by the language.
This is a problem when static objects are used by constructors used to initialize other static objects. Programs that depend on any particular order could work on one platform and crash on another. To ignore the problem is to ask for trouble.
Access to static object inside constructor
Suppose a constructor writes a message to cout . If the iostream library would not have provided a method for safe initialization of cout , such constructors would be dangerous to use for static objects.
#include <iostream.h> class EmcLog { public: EmcLog(ostream& out); // ... }; EmcLog::EmcLog(ostream& out) // ... { out << "Creating log" << endl; // ... } // cout must have been initialized before initializing // theLog. EmcLog theLog(cout); // static object
To avoid surprises, the programmer should document under what circumstances static objects, and/or function and classes that depend on them, can be used. In order to do that, the programmer must understand how static objects are initialized and how to control the initialization order.
You should always try to declare static objects initialized by constructors inside their corresponding access functions. These objects are guaranteed to be initialized before first use, because they are initialized when control passes through the function for the first time. This solution does not require the client to do anything special before using the function.
If using such access functions is not possible, consider using static pointers instead of objects, since that allows you to control how the objects are initialized. The simple rule is that before you use a function or a class that needs to use the static pointers to access objects, you must call a function that creates the objects bound to them.
In what way can that help? Since you do not depend on any implementation-defined order, your program will more portable. Another desirable property is that the client can control when the initialization function is called.
An initialization function often has a corresponding finalization function that should be called before terminating the program. By having an initialization class that manage the resources, the programmer can automatically get finalization by putting a call to the finalization function inside the destructor.
There are rules for how static objects within the same translation unit are initialized. If two static objects are defined within the same translation unit, but outside the scope of a function, their initialization order will be the same as the order of their definitions.
FOOTNOTE: This is the opposite to the rules for non-static data members where the declaration order, not the order of initializers, determines initialization order.
Initialization order of static objects
// sccsid initialized before release. namespace { const char sccsid[] = "@(#)myfile.cc ..."; const char release[] = "@(#)Emc Class Library, 1.2"; };
You can take advantage of this order when classes and functions require initialization. Many class libraries provides file local initialization objects within its header files to make sure that the classes can be used without trouble. This is what the iostream library does. This solution is safe, but costly in terms of performance and memory. Many small objects with constructors will be created before entering main and the number of objects will increase as the number of implementation files used to build the program gets bigger. For some applications this is not acceptable, so you should avoid such general solutions.
If you, before entering main() , want to access functions that depends on static objects, you must declare a static initialization object before first use of the class. Where to put that object should be your own responsibility.
Suppose you have a class EmcObject that requires initialization. The class provides a nested class Initor for that purpose. The implementation of Initor uses two member functions provided by EmcObject , initialize and finalize , that do the actual initialization and finalization of the class. An initialization object should be created before operating upon EmcObject objects.
class EmcObject { public: // ... class Initor { public: Initor(); ~Initor(); private: static int refcountM; }; friend class Initor; private: static void initialize(); static void finalize(); // ... };
The implementation must prevent a class to be initialized or finalized more than once. All EmcObject::Initor objects share a reference count that is updated each time an object is created. This is a common technique for safe initialization of static objects. By checking the value, we make sure that the class is only initialized and finalized once.
// EmcObject.cc int EmcObject::Initor::refcountM = 0; EmcObject::Initor::Initor() { if (refcountM == 0) EmcObject::initialize(); refcountM++; } EmcObject::Initor::~Initor() { refcountM--; if (refcountM == 0) EmcObject::finalize(); }
Before the client uses the class, an EmcObject::Initor object is created inside an unnamed namespace. By doing that, there is no risk of name clashes if more than one object with that name is created.
// client code namespace { EmcObject::Initor initor; // initializes EmcObject // ... } // more code ...