Static Members and Nested Classes

Class vs. Instance

A clarification of some terminology:

Intro to static members

If you create a class Midshipman, you will probably have a data member name. Each instance of class Midshipman has its own name, so that two different objects of type Midshipmancan (and usually do) have different names. Sometimes, however, we want to define data that is shared amongst all instances of the class - i.e. is global - rather than have each instance carry around its own copy of the data. For example, if you wanted to add the Supe's name as data belonging to the class, there should only be one Supe's name, and it should be shared by all instances of Midshipman. Imagine having each instance carry around his own copy of the Supe's name. It'd be possible for two different instances of Midshipman to disagree on who the Supe is!

This lecture will discuss how data, functions and types that are global to a class may be defined.

Class variables

A class variable is a piece of data that is global to a class. The ubiquitous keyword static, which means so many different things in so many different contexts, is used to declare a class variable. So, if a variable var is declared in the definition of class cls with the keyword static, you've essentially declared a global variable whose name is cls::var (inside the class you may simply refer to var), but which obeys the public/private access rules for classes.

Since class variables are very similar to global variables, you should treat them with the same level of apprehension, and only use them if you absolutely need to use them. There are likely other options available that will produce your desired result.

The syntax for creating and using class variables, as well as the reasons for wanting one in the first place, are probably best seen in an example. Consider our constant friend, the class Point. Suppose you wanted to keep a count of how many Point objects are alive at any point in the program - perhaps because you suspected you had a memory leak in a program (Quick: what does that mean for our usage of newand delete?). In the following version of the Point class, we declare a class variable count and use it to track the number of live Points. Every time a Point dies, we'll print out the number remaining.

ex1-point.h ex1-point.cpp ex1-main.cpp
#include <iostream>
using namespace std;

class Point {
private:
  double x, y;

  // This DECLARES the class variable
  // count.  It still needs to be
  // DEFINED (and initialized) in a
  // .cpp file.  count will keep track
  // of how many Point's are alive at
  // any given point in time.
  static int count;

public:
  Point(double a = 0, double b = 0);
  Point(const Point &P);
  ~Point();
  void readIn();
  void writeOut();
};
#include "ex1-point.h"

// This DEFINES the class variable
// Point::count (and initializes it)
int Point::count = 0;

Point::Point(double a, double b)
{ x = a; y = b; ++count; }     //keeping track

Point::Point(const Point &P)
{ x = P.x; y = P.y; ++count; } //keeping track

Point::~Point() {
  --count;                     //keeping track
  cout << "A Point dies, now there are "
       << count << endl;      //printing count
}

void Point::readIn() {
  char c;
  cin >> c >> x >> c >> y >> c;
}

void Point::writeOut() {
  cout << '(' << x << ','
       << y << ')' << endl;
}
#include "ex1-point.h"

int main() {
  Point *P = new Point[3];

  for(int i = 0; i < 3; i++)
    P[i].readIn();

  for(int i = 0; i < 3; i++)
    P[i].writeOut();

  delete P;

  return 0;
}

Below is a our program again that uses our Point class, along side sample run of the program. Our facility for tracking the number of live Points does indeed find a memory leak, meaning that we don't destroy all the Points our program creates. Only one Point dies before the program ends, two others remain and are never destroyed! Can you see why this happens?

In fact, if you run this code you'll notice that we get to the return statement just fine and the program attempts to clean up our memory after exiting main since the other two points still remain. However, the pointer to the Point array is no longer valid since we called delete on it, resulting in an invalid memory access and an error even though the program worked fine!

ex1-main.cpp (again) Sample run of program
#include "ex1-point.h"

int main() {
  Point *P = new Point[3];

  for(int i = 0; i < 3; i++)
    P[i].readIn();

  for(int i = 0; i < 3; i++)
    P[i].writeOut();

  delete P;

  return 0;
}
> g++ -o ex1 ex1-main.cpp ex1-point.cpp
> ./ex1
(1,3) (-2,0.5) (0,4.4)
(1,3)
(-2,0.5)
(0,4.4)
A Point dies, now there are 2
*** Error in `./ex1': munmap_chunk(): invalid pointer: 0x0000000001045c28 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f7e4d1077e5]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x1a8)[0x7f7e4d114698]
./ex1[0x400d76]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f7e4d0b0830]
./ex1[0x400bd9]
======= Memory map: ========
00400000-00402000 r-xp 00000000 00:38 3201
...
...
...
Aborted (core dumped)
>
Exercise:

What did you observe?

Class functions

Functions that are global to a class - i.e. that belong to the class but do not belong to any one instance in the same way as class variables - may be defined using our favorite keyword: static. A class function is essentially just a stand-alone function that:

  1. can only access static data members, other static member functions, and other non-member functions from outside the class
  2. can access the classes private data and member functions, assuming they are declared static
  3. can have access to it controlled via the public/private mechanism
  4. if public, can be called even if no objects (instances) of the class exist, and is accessible through its full name, i.e. the class name, followed by ::, followed by the name you've given the function
  5. can be access through the typical . operator if an object (instance) does exist

Exercise:

Defining classes within classes

We've seen that variables and functions that are global within a class can be defined. They're much like their truly global counterparts, except that their full names include classname::, and that access to them can be controlled via the public/private mechanism. Variables ... functions ... the only thing that's left are classes. Now we'll see that classes that are global to a class may also be defined. Such a nested class differs from a regular class in exactly the same two ways. This concept is really nice.

Suppose the definition of a class ClsB is nested within the definition of a class ClsA, as in the following:

class ClsA {
private:
  class ClsB {
  public:
    int x;
  private:
    int u;
  };

public:
  ClsB S;
  friend int f(ClsA G);
}; 

The name ClsB has no meaning on its own outside of the class ClsA. The true, full name of the class is actually ClsA::ClsB, i.e. "the class ClsB occurring within the class ClsA". If ClsB is really just a helper class for ClsA, then it probably shouldn't have an identity on its own, so using nested classes makes sense. It can also really help with walling off the user from the implementation, because the public/private access specifiers in ClsA can make ClsB inaccessible to the outside world.

Within ClsA, member data and functions of ClsB are accessible according to the same rules as if ClsB had been declared outside of any class definitions. Outside ClsB, accessibility of data in ClsB requires first applying the access rules for ClsA (i.e. is the definition of class ClsB accessible to me?) and then applying the accessibility rules for the particular member function or data in ClsB. For example, in the mainfunction

int main() {
  ClsA G;       // legal
  G.S.u = 0;    // access denied! u is private in ClsB
  G.S.x = 0;    // legal: S is a public instance of ClsB, and x is public in ClsB
  ClsA::ClsB H; // access denied! ClsB is private in ClsA, so you can't create instances of ClsB outside of ClsA
  return 0;
}   

However, inside the friend function f of ClsA, things are different. For example:

int f(ClsA G) {
  G.S.u = 0;    // access denied! u is private in ClsB
  G.S.x = 0;    // legal
  ClsA::ClsB H; // legal because f is declared as a friend within ClsA, so it can see ClsA's private parts (i.e. ClsB)
  return 0;
}
The LinkedList

The most obvious case of a class serving merely as a helper to another class, is the case of a LinkedList, where the Node class really only exists to make up the components of a LinkedList.

Now, suppose we create our List class to include a function which adds a value to the list and another which determines if a value exists in the list. Also, List will contain a nested and private Node class which a user will naturally have no access to. Therefore, we have the following elegant version of this class:

ex2-list.h ex2-list.cpp
#include <iostream>
using namespace std;

/********************************
 ** DEFINITION of class List ****
 ********************************/
class List {
public:
  // Default constructor
  List() { head = 0; }

  // Adds val to list
  void add(int val);

  // returns true if val in
  // list, false otherwise
  bool contains(int val);

  // Destructor
  ~List();

private:
  class Node {
  public:
    int data;
    Node *next;
    Node(int val, Node* p);
  };

  Node *head;
};
#include "ex2-list.h"

/** DEFINITION OF Node's MEMBERS **/
List::Node::Node(int val, Node* p) {
  data = val;
  next = p;
}

/** DEFINITION OF List's MEMBERS **/
void List::add(int val) {
  head = new Node(val,head);
}

bool List::contains(int val) {
  Node *p = head;
  while(p != 0 && p->data != val)
    p = p->next;
  return (p != 0);
}

List::~List() {
  while(head != 0) {
    Node *p = head;
    head = head->next;
    delete p;
  }
}

The file ex2-main.cpp (below) has a simple driver program that uses the List defined above. In this version of List, the user is actually unable to access any part of the class Node, which brings us closer to our goal of separating interface and implementation.

ex2-main.cpp

/* This program reads numbers and
   prints out the how many were
   read,ignoring duplicates */
#include "ex2-list.h"

int main() {
  // Initialize
  List L;
  int n, count = 0;

  // Read #s
  while(cin >> n)
    if (!L.contains(n))
    {
      L.add(n);
      count++;
    }

  // Print results
  cout << count
       << " unique numbers"
       << endl;
  return 0;
}