Team LiB
Previous Section Next Section

7.6. static Class Members

 

Classes sometimes need members that are associated with the class, rather than with individual objects of the class type. For example, a bank account class might need a data member to represent the current prime interest rate. In this case, we’d want to associate the rate with the class, not with each individual object. From an efficiency standpoint, there’d be no reason for each object to store the rate. Much more importantly, if the rate changes, we’d want each object to use the new value.

 

Declaring static Members

 

We say a member is associated with the class by adding the keyword static to its declaration. Like any other member, static members can be public or private. The type of a static data member can be const, reference, array, class type, and so forth.

 

As an example, we’ll define a class to represent an account record at a bank:

 

 

class Account {
public:
    void calculate() { amount += amount * interestRate; }
    static double rate() { return interestRate; }
    static void rate(double);
private:
    std::string owner;
    double amount;
    static double interestRate;
    static double initRate();
};

 

The static members of a class exist outside any object. Objects do not contain data associated with static data members. Thus, each Account object will contain two data members—owner and amount. There is only one interestRate object that will be shared by all the Account objects.

 

Similarly, static member functions are not bound to any object; they do not have a this pointer. As a result, static member functions may not be declared as const, and we may not refer to this in the body of a static member. This restriction applies both to explicit uses of this and to implicit uses of this by calling a nonstatic member.

 

Using a Class static Member

 

We can access a static member directly through the scope operator:

 

 

double r;
r = Account::rate(); // access a static member using the scope operator

 

Even though static members are not part of the objects of its class, we can use an object, reference, or pointer of the class type to access a static member:

 

 

Account ac1;
Account *ac2 = &ac1;
// equivalent ways to call the static member rate function
r = ac1.rate();      // through an Account object or reference
r = ac2->rate();     // through a pointer to an Account object

 

Member functions can use static members directly, without the scope operator:

 

 

class Account {
public:
    void calculate() { amount += amount * interestRate; }
private:
    static double interestRate;
    // remaining members as before
};

 

Defining static Members

 

As with any other member function, we can define a static member function inside or outside of the class body. When we define a static member outside the class, we do not repeat the static keyword. The keyword appears only with the declaration inside the class body:

 

 

void Account::rate(double newRate)
{
    interestRate = newRate;
}

 

Image Note

As with any class member, when we refer to a class static member outside the class body, we must specify the class in which the member is defined. The static keyword, however, is used only on the declaration inside the class body.

 

 

Because static data members are not part of individual objects of the class type, they are not defined when we create objects of the class. As a result, they are not initialized by the class’ constructors. Moreover, in general, we may not initialize a static member inside the class. Instead, we must define and initialize each static data member outside the class body. Like any other object, a static data member may be defined only once.

 

Like global objects (§ 6.1.1, p. 204), static data members are defined outside any function. Hence, once they are defined, they continue to exist until the program completes.

 

We define a static data member similarly to how we define class member functions outside the class. We name the object’s type, followed by the name of the class, the scope operator, and the member’s own name:

 

 

// define and initialize a static class member
double Account::interestRate = initRate();

 

This statement defines the object named interestRate that is a static member of class Account and has type double. Once the class name is seen, the remainder of the definition is in the scope of the class. As a result, we can use initRate without qualification as the initializer for rate. Note also that even though initRate is private, we can use this function to initialize interestRate. The definition of interestRate, like any other member definition, has access to the private members of the class.

 

Image Tip

The best way to ensure that the object is defined exactly once is to put the definition of static data members in the same file that contains the definitions of the class noninline member functions.

 

 

In-Class Initialization of static Data Members

 

Ordinarily, class static members may not be initialized in the class body. However, we can provide in-class initializers for static members that have const integral type and must do so for static members that are constexprs of literal type (§ 7.5.6, p. 299). The initializers must be constant expressions. Such members are themselves constant expressions; they can be used where a constant expression is required. For example, we can use an initialized static data member to specify the dimension of an array member:

 

 

class Account {
public:
    static double rate() { return interestRate; }
    static void rate(double);
private:
    static constexpr int period = 30;// period is a constant expression
    double daily_tbl[period];
};

 

If the member is used only in contexts where the compiler can substitute the member’s value, then an initialized const or constexpr static need not be separately defined. However, if we use the member in a context in which the value cannot be substituted, then there must be a definition for that member.

 

For example, if the only use we make of period is to define the dimension of daily_tbl, there is no need to define period outside of Account. However, if we omit the definition, it is possible that even seemingly trivial changes to the program might cause the program to fail to compile because of the missing definition. For example, if we pass Account::period to a function that takes a const int&, then period must be defined.

 

If an initializer is provided inside the class, the member’s definition must not specify an initial value:

 

 

// definition of a static member with no initializer
constexpr int Account::period; // initializer provided in the class definition

 

Image Best Practices

Even if a const static data member is initialized in the class body, that member ordinarily should be defined outside the class definition.

 

 

static Members Can Be Used in Ways Ordinary Members Can’t

 

As we’ve seen, static members exist independently of any other object. As a result, they can be used in ways that would be illegal for nonstatic data members. As one example, a static data member can have incomplete type (§ 7.3.3, p. 278). In particular, a static data member can have the same type as the class type of which it is a member. A nonstatic data member is restricted to being declared as a pointer or a reference to an object of its class:

 

 

class Bar {
public:
    // ...
private:
    static Bar mem1; // ok: static member can have incomplete type
    Bar *mem2;       // ok: pointer member can have incomplete type
    Bar mem3;        // error: data members must have complete type
};

 

Another difference between static and ordinary members is that we can use a static member as a default argument (§ 6.5.1, p. 236):

 

 

class Screen {
public:
    // bkground refers to the static member
    // declared later in the class definition
    Screen& clear(char = bkground);
private:
    static const char bkground;
};

 

A nonstatic data member may not be used as a default argument because its value is part of the object of which it is a member. Using a nonstatic data member as a default argument provides no object from which to obtain the member’s value and so is an error.

 

Exercises Section 7.6

 

Exercise 7.56: What is a static class member? What are the advantages of static members? How do they differ from ordinary members?

Exercise 7.57: Write your own version of the Account class.

Exercise 7.58: Which, if any, of the following static data member declarations and definitions are errors? Explain why.

 

// example.h
class Example {
public:
    static double rate = 6.5;
    static const int vecSize = 20;
    static vector<double> vec(vecSize);
};
// example.C
#include "example.h"
double Example::rate;
vector<double> Example::vec;

 

 
Team LiB
Previous Section Next Section