[ Team LiB ] |
![]() ![]() |
Gotcha #59: Initializing a Static Member in a ConstructorStatic data members exist independently of any object of their class and generally come into existence before any objects of the class. (Beware of constraints that are generally true.) Like member functions (both static and non-static), static data members have external linkage and occur in the scope of their class: class Account { // . . . private: static const int idLen = 20; static const int prefixLen; static long numAccounts; }; // . . . const int Account::idLen; const int Account::prefixLen = 4; long Account::numAccounts = 0; For constant integral and enum static members, initialization may take place within or outside the class but may occur only once. For constant integer values, it's often a reasonable alternative to use enumerators in place of initialized constant integers: class Account { // . . . private: enum { idLen = 20, prefixLen = 4 }; static long numAccounts; }; // . . . long Account::numAccounts = 0; The enumerators may generally be used in place of constant integers. However, they occupy no storage and therefore cannot be pointed to. They have a different type from int and therefore may affect function matching if they're used as actual arguments in the call of an overloaded function. Note also that while the definition of numAccounts outside the class was necessary, its explicit initialization was not. In that case, it would be initialized by default to "all zeros" or zero. However, the explicit initialization to zero is still a good idea, because it tends to forestall a maintainer's decision to initialize it to something else (1 and –1 are popular choices, for some reason). See also Gotcha #25. Runtime static initialization of static class members is a tremendously bad idea. The static member may be uninitialized at the time a static object of the class is itself initialized by a runtime static initialization: class Account { public: Account() { . . . calculateCount() . . . } // . . . static long numAccounts; static const int fudgeFactor; int calculateCount() { return numAccounts+fudgeFactor; } }; // . . . static Account myAcct; // oops! // . . . long Account::numAccounts = 0; const int Account::fudgeFactor = atoi(getenv("FUDGE")); The Account object myAcct is defined before the static data member fudgeFactor, so the constructor for myAcct will use an uninitialized fudgeFactor when it calls calculateCount (see Gotcha #55). The value of fudgeFactor will be zero, due to the default "all zeros" initialization of static data. If zero is a valid value for fudgeFactor, this bug may be difficult to detect. Some programmers try to circumvent this problem by "initializing" static data members within each of the class's constructors. This is impossible, since a static data member may not be present on a constructor's member initialization list, and once execution passes into the body of the constructor, initialization is no longer possible, only assignment:
Account::Account() {
// . . .
fudgeFactor = atoi( getenv( "FUDGE" ) ); // error!
}
The only alternative is to make fudgeFactor non-constant, write the code for "lazy initialization" (see Gotcha #3) in each of the class's constructors, and hope that any maintenance on the initialization code will be performed in parallel on all the constructors. It's best to treat static data members like other statics. Avoid them, if possible. If you must have them, initialize them, but avoid runtime static initialization, if possible. ![]() |
[ Team LiB ] |
![]() ![]() |