9.13. static Variables And Functions

Time: 00:05:18 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)
Review

Generally, each object instantiated from a class maintains its own distinct copy of each member variable declared in the class. So, in a sense, the object "owns" the member variables, and programs must access them through their owners. However, sometimes programs need to manage data that is accessible by various combinations of many objects (the Pouring Puzzle example presented later in the chapter illustrates this situation). We could implement the data as global variables, but we've seen that globals are error-prone, so we seek a more secure, object-oriented solution. The object-oriented programming languages implement the solution as class variables. Whereas member variables belong to specific objects, class variables belong to all instances of a given class. Like Java, C++ implements class variables with the static keyword (please see footnote 1).

static And UML Diagrams

Non-static or class members (attributes and operations) belong to individual instances of the class (i.e., objects). On the other hand, static or class features "belong" to the class. The contrast between the two ownerships constitutes a significant difference between static and non-static features. The UML provides additional notation to differentiate between the kinds of ownership. Unless otherwise specified, features in a UML class diagram are non-static and have instance (i.e., object) ownership. Alternatively, the UML denotes static features with class ownership by underlining the feature in the class diagram. Programmers translate the underlining to the static keyword when they translate the UML class diagram to C++.

The UML widget class diagram.
widget
-------------------
-count : int [underlined in the UML diagram]
-color : int
-alignment : float
-------------------
+widget()
+draw() : void
+get_count() : int [underlined in the UML diagram]
+get_color() : int
+set_color(a_color : int) : void
Denoting static features in a UML class diagram. The UML denotes static features by underlining them. The static keyword may modify attributes and operations alike and is independent of other modifiers such as public or private.

static Variables And Functions

To help us understand the role static features play in C++ programs, imagine that we want to write a program that counts the total number of objects it instantiates from class. Our approach is simple: define a variable named count to count each object as the program creates it. Consider the following problems that arise if we define count as a non-static or member variable:

We could define count outside the class, but this violates strong encapsulation. Good object-oriented design "hides" everything about a class in the class itself and provides controlled access through a public interface. Programmers use static variables whenever they need to use data related to a class but not bound to a specific object. This practice maintains strong encapsulation while addressing the problems listed above.

Although programs can access static variables through objects, these variables "belong" to a class. Consequently, they exist even when the program hasn't instantiated any objects from the class and after it has destroyed any objects. This observation implies that we need a class-level notation to manage static variables independently of any object. Furthermore, that notation must be sufficiently robust to support any functions that access the variables, including getter and setter functions. C++ bases the notation on the scope resolution operator, ::. When programmers use the scope resolution operator to define or use a variable or function, the code looks very much like it did when they used the operator with namespaces.

Foo.h Foo.cpp
class Foo
{
    private:
        int var1;
	int var2;
        static int count;

    public:
        Foo();
        static int get_count();
};

 
#include "Foo.h"

int Foo::count = 0;	// define and initialize

Foo::Foo()
{
	count++;
}

int Foo::get_count()
{
	return count;
}
(a)(b)
Client Memory Layout
#include <iostream>
#include "Foo.h"
using namespace std;

int main()
{
    Foo f0;
    Foo f1;
    Foo f2;

    cout << f0.get_count() << endl;	// works not preferred
    cout << Foo::get_count() << endl;	// preferred

    return 0;
}
A picture of three 'Foo' objects. Memory for the class variable 'count' is allocated outside and independent of the three objects.
(c)(d)
static example. C++ creates class variables with the static keyword. Programmers use class variables to save data that applies to all class instances rather than specific objects. The "skeletonized" code shown here is simplified to illustrate the concept and syntax of class variables and functions. Please see the Pouring Puzzle problem later in this chapter for an authentic example.
  1. The static keyword only appears in the class specification.
  2. Programmers define and initialize class variables in global scope (outside of any class or function) but restrict their scope by binding it to a class with the scope resolution operator. At this writing, C++ does not allow programmers to initialize static variables in the class specification.
  3. Although we can call static functions through an object, calling them with the class name is preferred. C++ only allows static functions to be called through the class name, so this notation signals a reader that the function is static and allows programmers to call the function when an object isn't available.
    • We can easily see that the call Foo::get_count() does not bind the function to an object.
    • But it's less obvious that the call f2.get_count() does not bind the object f2 to the function.
    static functions do not have a "this" pointer, and so cannot access any non-static class variables or functions.
  4. Programs allocate memory for static variables outside of all objects, which means that the variables exist in the absence of any objects. Programs can access the variable by name, but they can't access it via a "this" pointer because it is not part of an object.

Class Constants

A symbolic constant is an unchangeable value named for clarity or convenience. C++ inherits two ways of creating symbolic constants from the C programming language: create macros with #define or use enumerations. The first technique makes it difficult to restrict the constant to class scope, and the second technique is limited to integer constants. But we can also create constants with const keyword, and these can be any data type, follow all scoping rules, and can represent any valid data type. We can easily make these into class constants whose scope is limited to member functions by placing them inside a class.

class foo
{
    public:
        const static double MAGIC = 2.7;
	void function();
};
class bar
{
    public:
        const static double MAGIC = 3.14;
	void function();
};
(a)
void foo::function()
{
    ... MAGIC ...
}
void bar::function()
{
    ... MAGIC ...
}
(b)
void application1()
{
    ... foo::MAGIC ...
}
void application2()
{
    ... bar::MAGIC ...
}
(c)
Class constants. Two keywords, static and const, used in conjunction, form class constants. The code fragments illustrate the syntax for defining, initializing, and using class constants.
  1. Class constants are symbolic constants defined in class scope. Restricting their scope to a class allows multiple classes to have constants with the same name without any conflict or confusion. Programmers typically make class constants public, allowing application code to use them. Making class constants static makes them belong to the class as a whole - programs may use the constants independent of any object. Making them const prevents the application (or any other code) from changing the value.
  2. Member functions may use class constants without additional notation.
  3. Application or client code accesses a class constant with the class's name and the scope resolution operator.