Section 1 - Static Member Data
1B.1.1 Static Member Data
A static class member is data defined in the class (not local to any method) that is qualified with the static keyword. Instance members are those that do not have the static keyword. Let's review the difference between these two kinds of member data.
Each time we instantiate an object, a new set of instance members is associated with that object, distinct from the instance members of other objects of the class. If you have two Card objects, you have two separate sets of data, one for each object.
In contrast, with static class members, there is only one datum associated with the entire class. If a member of a class is qualified by the static keyword, then no matter how many objects we instantiate, there is only one variable for that member, shared by all the objects of the class.
Why would we want such a communistic variable to be in our class? There could be many reasons. Here is an example of just one:
class Dog { public: static long population; // static member long licenseNumber; // instance member // other members ... }; // later in the client or main method ... Dog fido, lucy, watson;
We have declared three new Dog objects - in other words, we instantiated 3 dogs. fido, lucy and watson each has its own personal copy of the licenseNumber data because licenseNumber is an instance member. They all share the one and only static member, population. This might be useful if, for instance, we wanted the population to be incremented each time a Dog was born, and decremented each time a dog died. It is a value that is common to all dogs in the Dog class. But the licenseNumber is unique to each instance:
fido.licenseNumber= 2312;
Clearly makes sense to us. It is saying that the licenseNumber field (I'll switch terminology to make sure you get used to both terms: member and field) in the fido instance is being set to 2312. But what does:
watson.population++;
do? Well it clearly increments population, but since population is a static class member, there is only one such field for the entire class, and we are modifying that field. Since there is no reason to access this field through use of the watson reference, any more than there is to use the fido or lucy reference, we are offered the preferred method of access to static class members, through the class name:
Dog::population++;
Here we see more clearly what we are doing. Usually, you will want to use the class name when dereferencing a static class field.
1B.1.2 Initializing Static Member Data
As you know, we have a special method whose job it is to initialize all the instance member data for us. That method is called a constructor, and it always has the same name as the name of the class.
Card card1, card2('5', spades);
These two objects now have their private data set because of the Card constructor (go back and see what that constructor did, if you forgot).
Now that we have this concept of a static member, however, we have to figure out how to initialize it. Since it is a public member, we could have our client do it with a simple assignment statement, Dog::population = 0;, but we should really make population private. Private instance data is usually initialized in the constructor, but we can't use a constructor for private static data . Can anyone tell us why that would mess up the concept of the Dog population? C++ gives us a somewhat awkward way to initialize static class members. After the class prototype, you would dereference the static member with the class name and initialize it like so:
class Dog { private: static long population; // private static member long licenseNumber; // private instance member }; // initialize static member long Dog::population = 0;
When the program starts, population will be set to 0. That assignment is not visited ever again, so population can be adjusted as Dogs are created or destroyed.
Initializing a Static Array
Initialization of static arrays usually gives students panic attacks. Depending on the array and situation, you can do it in couple of different ways. If the array is reasonably short, you might fill it with data entirely in the initialization statement (outside the class prototype) as in:
string StudentClass::firstNames[] = {"elaine", "shailesh", "ying","sandra", "marlin"};
Another idea, especially if the array is long or the type is some massive object, is to use a pro forma initialization, which satisfies the compiler, but does nothing as far as initializing the array:
string StudentClass::firstNames[] = {0};
The 0 inside the braces will only work if it is compatible with the array element type. Surprisingly, 0 is compatible with string, char and several types not normally considered integer. If this does not work, simply supply some constant of the correct type (Days::monday), which might involve building an automatic object (Card('3', Card::clubs)). You have to accompany this with an initialization somehow, and there are a variety of ways to do it. Here are two common choices:
- Provide a public static method for the client to call at the start of its program, say StudentClass::initMyStaticArrays(), which loops through the static array(s) and initializes it (them).
The problem with this is that the client may forget to call initMyStaticArrays(), in which case, the program will crash. But it is a legitimate option. - Provide the same method, but call it from the constructor.
What! you say? Doesn't that violate the rule not to initialize statics in a constructor, and won't it cause us to lose good data every time a new object is constructed? Not if you are careful and clever:
Within the initMyStaticArrays(), declare a static local variable, static bool firstTime = true;. Test this member on entry, and if it is true, let the method proceed, but before you return set it to false. If it is false, just return without doing the initialization. - Don't initiliaze it at all. This is a simpler version of the = {0} option we discussed above, and works for any type, not just int-compatible types. You don't really need to initialize all static data, you just have to declare it at global scope (to satisfy the linker). So you can simply use the syntax:
string StudentClass::firstNames[];
without an = operator after it. This establishes the static so that the linker is satisfied. In fact, you can do this with any static if you want to just get past the linker errors but don't need to put initial values into a particular static. When would you not want to initialize a static? Maybe the client will, in the normal course of using the class, call certain static methods which fill the static data with values in an orderly manner. An array may not be accessed until something is "pushed" onto it, and a static member like numElementsInArray might track how many elements are stored there. numElementsInArray might be 0, initially (this member you would want to initialize), so we would not try to access any array elements until the client does something that adds to the array.