Static Members: Can Fabric Softener Help? - Introduction to Classes - C++ For Dummies (2014)

C++ For Dummies (2014)

Part III

Introduction to Classes

Chapter 18

Static Members: Can Fabric Softener Help?

In This Chapter

arrow Declaring static member data

arrow Defining and using static member functions

arrow Understanding why my static member function can’t call my other member functions

By default, data members are allocated on a per-object basis. For example, each person has his or her own name. You can also declare a member to be shared by all objects of a class by declaring that member static. The term static applies to both data members and member functions, although the meaning is slightly different. This chapter describes both types, beginning with static data members.

Defining a Static Member

The programmer can make a data member common to all objects of the class by adding the keyword static to the declaration. Such members are called static data members. (I would be a little upset if they were called something else.)

Why you need static members

Most properties are properties of the object. Using the well-worn (one might say, threadbare) student example, properties such as name, ID number, and courses are specific to the individual student. However, all students share some properties — for example, the number of students currently enrolled, the highest grade of all students, or a pointer to the first student in a linked list.

It’s easy enough to store this type of information in a common, ordinary, garden-variety global variable. For example, you could use a lowly int variable to keep track of the number of Student objects. The problem with this solution, however, is that global variables are outside the class. It’s like putting the voltage regulator for my microwave outside the enclosure. Sure, I could do it, and it would probably work — the only problem is that I wouldn't be too happy if my dog got into the wires and I had to peel him off the ceiling (the dog wouldn’t be thrilled about it, either).

If a class is going to be held responsible for its own state, objects such as global variables must be brought inside the class, just as the voltage regulator must be inside the microwave lid, away from prying paws. This is the idea behind static members.

image You may hear static members referred to as class members; this is because all objects in the class share them. By comparison, normal members are referred to as instance members, or object members, because each object receives its own copy of these members.

Using static members

A static data member is one that has been declared with the static storage class, as shown here:

class Student
{
public:
Student(char *pName = "no name") : name(pName)
{
noOfStudents++;
}
~Student(){ noOfStudents--; }

static int noOfStudents;
string name;
};

Student s1;
Student s2;

The data member noOfStudents is part of the class Student but is not part of either s1 or s2. That is, for every object of class Student, there is a separate name, but there is only one noOfStudents, which all Students must share.

“Well then,” you ask, “if the space for noOfStudents is not allocated in any of the objects of class Student, where is it allocated?” The answer is, “It isn’t.” You have to specifically allocate space for it, as follows:

int Student::noOfStudents = 0;

This somewhat peculiar-looking syntax allocates space for the static data member and initializes it to 0. (You don't have to initialize a static member when you declare it; C++ will invoke the default constructor if you don't.) Static data members must be global — a static variable cannot be local to a function.

image The name of the class is required for any member when it appears outside its class boundaries.

image This business of allocating space manually is somewhat confusing until you consider that class definitions are designed to go into files that are included by multiple source code modules. C++ has to know in which of those .cpp source files to allocate space for the static variable. This is not a problem with non-static variables because space is allocated in every object created.

Referencing static data members

The access rules for static members are the same as the access rules for normal members. From within the class, static members are referenced like any other class member. Public static members can be referenced from outside the class, whereas well-protected static members can’t. Both types of reference are shown in the following code snippet using the declaration of Student from the previous section:

void fn(Student& s1, Student& s2)
{
// reference public static
cout << "No of students "
<< s1.noOfStudents // reference from outside
<< endl; // of the class
}

In fn(), noOfStudents is referenced using the object s1. But s1 and s2 share the same member noOfStudents. How did I know to choose s1? Why didn’t I use s2 instead? It doesn’t make any difference. You can reference a static member using any object of that class.

In fact, you don’t need an object at all. You can use the class name directly instead, if you prefer, as in the following:

// ...class defined the same as before...
void fn(Student& s1, Student& s2)
{
// the following produce identical results
cout << "Number of students "
<< Student::noOfStudents
<< endl;
}

If you do use an object name when accessing a static member, C++ uses only the declared class of the object.

image This is a minor technicality, but in the interest of full disclosure: The object used to reference a static member is not evaluated even if it’s an expression. For example, consider the following case:

class Student
{
public:
static int noOfStudents;
Student& nextStudent();
// ...other stuff the same...
};

void fn(Student& s)
{
cout << s.nextStudent().noOfStudents << "\n"
}

The member function nextStudent() is not actually called. All C++ needs to access noOfStudents is the return type, and it can get that without bothering to evaluate the expression. This is true even if nextStudent() should do other things, such as wash windows or shine your shoes. None of those things will be done. Although the example is obscure, it does happen. That’s what you get for trying to cram too much stuff into one expression.

Uses for static data members

Static data members have umpteen uses, but let me touch on a few here. First, you can use static members to keep count of the number of objects floating about. In the Student class, for example, the count is initialized to 0, the constructor increments it, and the destructor decrements it. At any given instant, the static member contains the count of the number of existing Student objects. Remember, however, that this count reflects the number of Student objects (including any temporaries) and not necessarily the number of students.

A closely related use for a static member is as a flag to indicate whether a particular action has occurred. For example, a class Radio may need to initialize hardware before sending the first tune command but not before subsequent tunes. A flag indicating that this is the first tune is just the ticket. This includes flagging when an error has occurred.

Another common use is to provide space for the pointer to the first member of a list — the so-called head pointer (see Chapter 13 if this doesn't sound familiar). Static members can allocate bits of common data that all objects in all functions share (overuse of this common memory is a really bad idea because doing so makes tracking errors difficult).

Declaring Static Member Functions

Member functions can be declared static as well. Static member functions are useful when you want to associate an action to a class, but you don’t need to associate that action with a particular object. For example, the member function Duck::fly() is associated with a particular duck,whereas the rather more drastic member function Duck::goExtinct() is not.

Like static data members, static member functions are associated with a class and not with a particular object of that class. This means that, like a reference to a static data member, a reference to a static member function does not require an object. If an object is present, only its type is used.

Thus, both calls to the static member function number() in the following example are legal. This brings us to our first static program — I mean our first program using static members — CallStaticMember:

// CallStaticMember - demonstrate two ways to call a
// static member function
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

class Student
{
public:
Student(const char* pN = "no name") : sName(pN)
{
noOfStudents++;
}
~Student() { noOfStudents--; }
const string& name() { return sName; }
static int number() { return noOfStudents; }

protected:
string sName;
static int noOfStudents;
};
int Student::noOfStudents = 0;

int main(int argcs, char* pArgs[])
{
// create two students and ask the class "how many?"
Student s1("Chester");
Student* pS2 = new Student("Scooter");

cout << "Created " << s1.name()
<< " and " << pS2->name() << endl;
cout << "Number of students is "
<< s1.number() << endl;

// now get rid of a student and ask again
cout << "Deleting " << pS2->name() << endl;
delete pS2;
cout << "Number of students is "
<< Student::number() << endl;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

This program creates two Student objects, one locally and one off the heap. It then displays their names and the count of the number of students. Next the program deletes one of the students and asks the class how many students are out there. The output from the program appears as follows:

Created Chester and Scooter
Number of students is 2
Deleting Scooter
Number of students is 1
Press any key to continue...

This class keeps its data members protected and provides access functions that allow outside (non-Student) code to read but not modify them.

image Declaring the return type of name() method to be string& rather than simply string causes the function to return a reference to the object's existing name rather than create a temporary string object. (See Chapter 17 for a brilliant treatise on constructing and avoiding temporaries.) Adding the const to the declaration keeps the caller from modifying the class's name member.

Notice how the static member function number() can access the static data member noOfStudents. In fact, that's the only member of the class that it can access — a static member function is not associated with any object. Were I to declare name() to be static, I could refer toStudent::name(), which would immediately beg the question, “Which name?”

The following snippet is only one case that I'm aware of where a static method can refer directly to a non-static member:

class Student
{
public:
static int elementsInName()
{
int sizeOfArray = sizeof(name);
return sizeOfArray/sizeof(char);
}

protected:
char name[MAX_NAME_SIZE];
};

image Here the static method elementsInName() refers to name without referencing any object. This was not legal prior to the 2011 standard. It's allowed now because the sizeof name is the same for all objects. Thus, it doesn't matter which object you refer to.

image You may wonder why I divided sizeof(name) by sizeof(char). The sizeof(name) returns the number of bytes in the array name. But what we want is the number of elements in name, so we have to divide by the size of each element in name. But isn't sizeof(char) equal to 1? Well, maybe, but maybe not. Dividing the sizeof the array by the sizeof a single element always works for all array types.

What Is this About Anyway?

How does a non-static object method know what object it's referring to? In other words, when I ask the Student object for its name, how does name() know which sName to return?

The address of the current object is passed as an implied first argument to every non-static method. When it is necessary to refer to this object, C++ gives it the name this. this is a keyword in every object method meaning "the current object.” This is illustrated in the following code snippet:

class SC
{
public:
void dyn(int a); // like SC::dyn(SC *this, int a)
static void stat(int a); // like SC::stat(int a)
};

void fn(SC& s)
{
s.dyn(10); // -converts to-> SC::dyn(&s, 10);
s.stat(10); // -converts to-> SC::stat(10);
}

That is, the function dyn() is interpreted almost as though it were declared void SC::dyn(SC *this, int a). The call to dyn() is converted by the compiler as shown, with the address of s passed as the first argument. (You can’t actually write the call this way, but this is what the compiler is doing.)

References to other non-static members within SC::dyn() automatically use the this argument as the pointer to the current object. When SC::stat() was called, no object address was passed. Thus, it has no this pointer to use when referencing non-static functions, which is why I say that a static member function is not associated with any current object.

You can see this used explicitly in an object-oriented version of the linked list program from Chapter 13; called LinkedLIstData. The entire program is available with the online material at www.dummies.com/extras/cplusplus; the NameDataSet class appears here:

// NameDataSet - stores a person's name (these objects
// could easily store any other information
// desired).
class NameDataSet
{
public:
NameDataSet(string& refName)
: sName(refName), pNext(nullptr) {}

// add self to beginning of list
void add()
{
this->pNext = pHead;
pHead = this;
}

// access methods
static NameDataSet* first() { return pHead; }
NameDataSet* next() { return pNext; }
const string& name() { return sName; }
protected:
string sName;

// the link to the first and next member of list
static NameDataSet* pHead;
NameDataSet* pNext;
};

// allocate space for the head pointer
NameDataSet* NameDataSet::pHead = nullptr;

Here you can see that the pHead pointer to the beginning of the list has been converted into a static data member because it applies to the entire class. In addition, pNext has been made a data member and access methods have been provided to give other programs access to the now protected members of the class.

The add() method adds the current object to the list by first setting its pNext pointer to the beginning of the list. The next statement causes the head pointer to point to the current object via the assignment pHead = this.