MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Static members in C++

There are times when you need a single storage space to be used by all objects of a class. In C, you would use a global variable, but this is not very safe. Global data can be modified by anyone, and its name can clash with other identical names in a large project. It would be ideal if the data could be stored as if it were global, but be hidden inside a class, and clearly associated with that class.

This is accomplished with static data members inside a class. There is a single piece of storage for a static data member, regardless of how many objects of that class you create. All objects share the same static storage space for that data member, so it is a way for them to “communicate” with each other. But the static data belongs to the class; its name is scoped inside the class and it can be public, private, or protected.

Defining storage for static data members

Because static data has a single piece of storage regardless of how many objects are created, that storage must be defined in a single place. The compiler will not allocate storage for you, although this was once true, with some compilers. The linker will report an error if a static data member is declared but not defined.

The definition must occur outside the class (no inlining is allowed), and only one definition is allowed. Thus it is usual to put it in the implementation file for the class. The syntax sometimes gives people trouble, but it is actually quite logical. For example,

class A {
  static int i;
public:
  //...
};

and later, in the definition file,

int A::i = 1;

If you were to define an ordinary global variable, you would say

int i = 1;

but here, the scope resolution operator and the class name are used to specify A::i.

Some people have trouble with the idea that A::i is private, and yet here’s something that seems to be manipulating it right out in the open. Doesn’t this break the protection mechanism? It’s a completely safe practice for two reasons. First, the only place this initialization is legal is in the definition. Indeed, if the static data were an object with a constructor, you would call the constructor instead of using the = operator. Secondly, once the definition has been made, the end-user cannot make a second definition – the linker will report an error. And the class creator is forced to create the definition, or the code won’t link during testing. This ensures that the definition happens only once and that it’s in the hands of the class creator.

The entire initialization expression for a static member is in the scope of the class. For example,

//: C10:Statinit.cpp
// Scope of static initializer
#include <iostream>
using namespace std;

int x = 100;

class WithStatic {
  static int x;
  static int y;
public:
  void print() const {
    cout << "WithStatic::x = " << x << endl;
    cout << "WithStatic::y = " << y << endl;
  }
};

int WithStatic::x = 1;
int WithStatic::y = x + 1;
// WithStatic::x NOT ::x

int main() {
  WithStatic ws;
  ws.print();
} ///:~ 

Here, the qualification WithStatic:: extends the scope of WithStatic to the entire definition.

static array initialization

It’s possible to create static const objects as well as arrays of static objects, both const and non- const. Here’s the syntax you use to initialize such elements:

//: C10:Statarry.cpp {O}
// Initializing static arrays

class Values {
  static const int size;
  static const float table[4];
  static char letters[10];
};

const int Values::size = 100;

const float Values::table[4] = {
  1.1, 2.2, 3.3, 4.4
};

char Values::letters[10] = {
  'a', 'b', 'c', 'd', 'e',
  'f', 'g', 'h', 'i', 'j'
};
///:~

As with all static member data, you must provide a single external definition for the member. These definitions have internal linkage, so they can be placed in header files. The syntax for initializing static arrays is the same as any aggregate, but you cannot use automatic counting. With the exception of the above paragraph, the compiler must have enough knowledge about the class to create an object by the end of the class declaration, including the exact sizes of all the components.

Compile-time constants inside classes

In Chapter 6 enumerations were introduced as a way to create a compile-time constant (one that can be evaluated by the compiler in a constant expression, such as an array size) that’s local to a class. This practice, although commonly used, is often referred to as the “enum hack” because it uses enumerations in a way they were not originally intended.

To accomplish the same thing using a better approach, you can use a static const inside a class. [29] Because it’s both const (it won’t change) and static (there’s only one definition for the whole class), a static const inside a class can be used as a compile-time constant, like this:

class X {
  static const int size;
  int array[size];
public:
  // ...
};

const int X::size = 100; // Definition 

If you’re using it in a constant expression inside a class, the definition of the static const member must appear before any instances of the class or member function definitions (presumably in the header file). As with an ordinary global const used with a built-in type, no storage is allocated for the const, and it has internal linkage so no clashes occur.

An additional advantage to this approach is that any built-in type may be made a member static const . With enum, you’re limited to integral values.

Nested and local classes

You can easily put static data members in that are nested inside other classes. The definition of such members is an intuitive and obvious extension – you simply use another level of scope resolution. However, you cannot have static data members inside local classes (classes defined inside functions). Thus,

//: C10:Local.cpp {O}
// Static members & local classes
#include <iostream>
using namespace std;

// Nested class CAN have static data members:
class Outer {
  class Inner {
    static int i; // OK
  };
};

int Outer::Inner::i = 47;

// Local class cannot have static data members:
void f() {
  class Local {
  public:
//! static int i;  // Error
    // (How would you define i?)
  } x;
} ///:~ 

You can see the immediate problem with a static member in a local class: How do you describe the data member at file scope in order to define it? In practice, local classes are used very rarely.

static member functions

You can also create static member functions that, like static data members, work for the class as a whole rather than for a particular object of a class. Instead of making a global function that lives in and “pollutes” the global or local namespace, you bring the function inside the class. When you create a static member function, you are expressing an association with a particular class.

You can call a static member function in the ordinary way, with the dot or the arrow, in association with an object. However, it’s more typical to call a static member function by itself, without any specific object, using the scope-resolution operator, like this:

class X {
public:
  static void f();
};

X::f();

When you see static member functions in a class, remember that the designer intended that function to be conceptually associated with the class as a whole.

A static member function cannot access ordinary data members, only static data members. It can call only other static member functions. Normally, the address of the current object ( this) is quietly passed in when any member function is called, but a static member has no this, which is the reason it cannot access ordinary members. Thus, you get the tiny increase in speed afforded by a global function, which doesn’t have the extra overhead of passing this, but the benefits of having the function inside the class.

For data members, static indicates that only one piece of storage for member data exists for all objects of a class. This parallels the use of static to define objects inside a function, to mean that only one copy of a local variable is used for all calls of that function.

Here’s an example showing static data members and static member functions used together:

//: C10:StaticMemberFunctions.cpp

class X {
  int i;
  static int j;
public:
  X(int ii = 0) : i(ii) {
     // Non-static member function can access
     // static member function or data:
    j = i;
  }
  int val() const { return i; }
  static int incr() {
    //! i++; // Error: static member function
    // cannot access non-static member data
    return ++j;
  }
  static int f() {
    //! val(); // Error: static member function
    // cannot access non-static member function
    return incr(); // OK -- calls static
  }
};

int X::j = 0;

int main() {
  X x;
  X* xp = &x;
  x.f();
  xp->f();
  X::f(); // Only works with static members
} ///:~ 

Because they have no this pointer, static member functions can neither access non static data members nor call non static member functions. (Those functions require a this pointer.)

Notice in main( ) that a static member can be selected using the usual dot or arrow syntax, associating that function with an object, but also with no object (because a static member is associated with a class, not a particular object), using the class name and scope resolution operator.

Here’s an interesting feature: Because of the way initialization happens for static member objects, you can put a static data member of the same class inside that class. Here’s an example that allows only a single object of type egg to exist by making the constructor private. You can access that object, but you can’t create any new egg objects:

//: C10:Selfmem.cpp
// Static member of same type
// ensures only one object of this type exists.
// Also referred to as a "singleton" pattern.
#include <iostream>
using namespace std;

class Egg {
  static Egg e;
  int i;
  Egg(int ii) : i(ii) {}
public:
  static Egg* instance() { return &e; }
  int val() { return i; }
};

Egg Egg::e(47);

int main() {
//!  Egg x(1); // Error -- can't create an Egg
  // You can access the single instance:
  cout << Egg::instance()->val() << endl;
} ///:~ 

The initialization for E happens after the class declaration is complete, so the compiler has all the information it needs to allocate storage and make the constructor call.


[29] Your compiler may not have implemented this feature yet; check your local documentation.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru