10.5. Class Definition

We have already looked at some examples of C++ class declarations. A class contains both member data variables and member functions. The code for a member function can either be listed in the class declaration or can be separate with the class declaration listing a function prototype.

Class member variables can either be public or private. C++ also has protected member elements. Using protected access offers an intermediate level of protection between public and private access if the class is inherited by other classes. A base class’s protected members can be accessed by member and friends of that base class and by members and friends of any classes derived from that base class.

Since I just opened a can of worms by mentioning friends, a friend is a C function or C++ class that may access the private members of the base class. Only the base class can grant friendship.

class yourfiend {
   friend void somefunction(int);  // A C function
   friend class anotherclass;
private:
   int data;
}

10.5.1. Constructors and Destructors

We can define code which is executed whenever an instance of a class (object) is allocated (a constructor) and other code which is execute just before deallocating an object (a destructor).

Generally, the constructor is used to initialize some data. There can be more than one constructor with different input arguments. Sometimes we know different pieces of information about the object at the time it is created.

The destructor may wish to do some clean up so that other data structures or objects don’t think the object still exists. One of the most common jobs of a destructor is to deallocate extra memory which may be pointed to by the object. If the object has a pointer which points to data which was allocated dynamically, then if the destructor does not deallocate the memory, a memory leak will exist.

A constructor is a function with the same name as the class.

class String {
public:
   // constructors
   String() : buffer(0) {}  // init buffer to null
   String( const char * right ) : buffer(0)
      { resize(strlen(right)); strcpy(buffer, right); }
   String( const String * right ) : buffer(0)
      { resize(strlen(right->buffer));
        strcpy(buffer, right->buffer);
      }

   // destructor
   ~String() { delete [] buffer; }

   //assignment
   void operator = (const String & right)
      { resize( strlen(right.buffer));
        strcpy( buffer, right.buffer );
      }

private:
   void resize (int size);
   char * buffer;
};

void String::resize(int size)
{
   if ( buffer == 0 ) // no previous allocation
      buffer = new char[1 + size];
   else if ( size > strlen(buffer)) {
      delete [] buffer;
      buffer = new char[1 + size];
   }
}

10.5.2. Initalization lists

In the above example we initalize buffer to NULL (zero) in the constructor, but not with an assignment statement in the body of the constructor.

String() : buffer(0) {}  // init buffer to null

This is called an initalization list. In fact, constructors should initialize all member objects in the initialization list.

For example, this constructor initializes member object x using an initialization list: Fred::Fred() : x(whatever) { }. The most common benefit of doing this is improved performance. For example, if the expression whatever is the same as member variable x, the result of the whatever expression is constructed directly inside x. The compiler does not make a separate copy of the object. Even if the types are not the same, the compiler is usually able to do a better job with initialization lists than with assignments.

The other (inefficient) way to build constructors is via assignment, such as: Fred::Fred() { x = whatever; }. In this case the expression whatever causes a separate, temporary object to be created, and this temporary object is passed into the x object’s assignment operator. Then that temporary object is destructed at the ;.

10.5.3. Initalizing parent classes

The constructor for a child class can supply an initialization value to the constructor of a parent class in the initialization list.

class bigbox : public box {  // C++ code
public:
   bigbox( int x, double d ) : box(x), dvalue(d) { }
private:
   double dvalue;
};

The code box(x) calls the constructor for the parent class.

10.5.4. Common constructor code

Multiple constructors may be defined to accommodate different arguments. However only one constructor is called to initialize the object, and one constructor may not call another constructor. So if a nontrivial amount of code is common to more than one constructor, one of two solutions is used so that the code need not be duplicated.

The first approach is to have a default value for one or more constructor argument to reduce the number of constructors needed. Here the same constructor is used if or two variables are passed to the constructor. If one variable is used, then the variable j is given a value of 7.

class newclass {
public:
   newclass ( int i, int j = 7 )
   {
      // object initialization code
   }
};

If the above scheme does not work, then an extra function is often defined to do part of the work of the constructors.

class newclass {
public:
   newclass ( int i )
   {
      initalize(i)  // call common code
      // specific object initialization code
   }
   newclass ( int i, int j )
   {
      initalize(i)  // call common code
      // specific object initialization code
   }
private:
   void initialize( int i ) {
      // common constructor code goes here
   }
};

10.5.5. Nested Classes

Here is the partial implementation of a doubly linked list class. The outer class manages the whole linked list while the inner class is for a specific node of the list.

class List {
private:
   class Link;  // forward definition
   Link *firstElement;

   class Link { // nested class definition
   public:
      int value;
      Link *forwardLink;
      Link *backwardLink;

      Link( int v, Link *f, Link *b )
         {  value=v; forwardLink=f; backwardLink=b; }

      void addBefore( int val )
      {
         Link *newLink = new Link( val, this, backwardLink );
         if( backwardLink == 0 )
            firstElement = newLink;
         else {
            backwardLink->forwardLink = newLink;
         }
         backwardLink = newLink;
      }

      // other methods

   };

public:
   void push_front(int val)
   {
      if( firstElement == 0 )
         firstElement = new Link( val, 0, 0 );
      else
         firstElement->addBefore( val );
   }

   // other methods
};