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).
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++.
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; } |
|
(c) | (d) |
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.
static
keyword only appears in the class specification.static
variables in the class specification.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.
static
functions do not have a "this" pointer, and so cannot access any non-static
class variables or functions.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.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) |
static
and const
, used in conjunction, form class constants. The code fragments illustrate the syntax for defining, initializing, and using 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.