Classes in C++.

Class definition

To define a new class C++ provides the following syntax:
class class_name {
   public:
      declarations of visible data members and methods
   private:
      declarations of hidden data members and methods
   protected:
      declarations of data members and methods visible to derived classes only
};
In the public section we declare all methods and attributes available to any other object, and in the private section we declare those what can be accessed only by the methods of this class. The following example illustrates the definition of the new class MovingObject. The complete definition of a new class usually consists of two parts: definition and implementation. In the definition we describe the structure of the class
class MovingObject {
   public:
      enum Direction {North, West, South, East}; // declaring a new type for directions
      MovingObject();
      int getX();
      int getY();
      void step();
      void speed_up();
      void slow_down();
      void turn(Direction new_dir);
   private:
      int x, y;  // current coordinates of the object
      int dir;   // direction of the object
      int speed; // speed of the object
};
Class definition
and in the implementation section we put code for all methods of the class.
MovingObject::MovingObject()
{
  x = y = 0;
  speed = 1;
  dir = South;
}

inline int MovingObject::getX()
{
  return x;
}

inline int MovingObject::getY()
{
  return y;
}

void MovingObject::step()
{
  switch( dir ){
    case North: y += speed; break;
    case West:  x -= speed; break;
    case South: y -= speed; break;
    case East:  x += speed; break;
  }
}

inline void MovingObject::speed_up()
{
  speed++;
}

inline void MovingObject::slow_down()
{
  if( speed ) speed--;   // decrease speed only if it's greater than zero
}

inline void MovingObject::turn(Direction new_dir)
{
  dir = new_dir;
}
Class methods' implementation

In the definition of the MovingObject class we would like you to pay attention to the line

enum Direction {North, West, South, East};
In this line we define a new enumerated type Direction that can take one of the possible 4 values (North, West, South, and East). In fact, it was done only to define four new constants available both inside and outside the class. Since the type is defined in the public section of the class definition these constants can be accessed from objects of other types as MovingObject::West, MovingObject::East, etc. We need to use this unusual way to define constants because C++ syntax doesn't allow us to define constants using the const keyword inside classes.

In the implementation section we would like you to notice the keyword inline in front of some methods. To explain the difference between normal methods and inline methods we need to recall how the C++ compiler translates the member functions into machine code. When a function call is made, the compiler usually generates the machine code that jumps to another place in the program code where there is the code for the function called. When the machine code for the function has been executed, there is a jump back in the program to the position where the call was made. The machine code for the function itself is therefore found in only one position, and all calls will result in a jump to that position. Due to this procedure, for each call there should be a set of instructions that allow to save the address of the point the call is made from and all parameters for the function is a special location (program stack) before the jump is made to ensure the jump back to the right place. If a function is small (consists of one or two statements), then amount of service instructions to perform the call is comparable to the amount of instructions in the function itself. When this is the case it would be more efficient to execute the machine code for the function in the place where the call is made instead of jumping to another location. This is can be done using the inline functions. If a function specified to be inline, then the machine code for the function is duplicated at every location where program calls the function. Another way to make a class method to be inline is to put its implementation directly in the definition of the class.

Please find the complete example on the example page.

Placement of definition and implementation

Once we know hot to define a new class and now we would like to discuss the next question: "How to divide a big program into a set of source files?" It's very clear that we can keep definitions and implementations for all classes within one file only for very small projects. That is, we need to find a way to separate different parts of the program into smaller source files. Traditionally, two separate files created for each new class defined in a program. The first file (header file) contains only the definition of the class and the second file contains the implementations of the class methods. It is also convenient to give these files the same name as the name of the class with extensions .h (for the header file) and .cpp (for the file with the implementation code).

Thus, for our example we can create the following header file movingobject.h

#ifndef _MOVINGOBJECT_H
#define _MOVINGOBJECT_H
class MovingObject {
   public:
      enum Direction {North, West, South, East}; // declaring a new type for directions
      MovingObject();
      ...
   private:
      int x, y;
      ...
};

// all inline functions

#endif
File movingobject.h
Here we would like to note two important things. First of all, we add three lines with preprocessor directives (the ones started with #). They are added to avoid recompiling the header file if it's included in more than one source file. The first directive:
#ifndef _MOVINGOBJECT_H
makes sure that variable _MOVINGOBJECT_H is not yet defined. If it is, then this file will not be compiled again. Thus, the following lines will be processed only if this is the first time the compiler reads the file. The next line
#define _MOVINGOBJECT_H
defines the variable _MOVINGOBJECT_H to avoid future recompiling of the file. And the second important thing we would like to notice here is, that the header file should include not only the definition of the class, but also the definition of all inline functions. Only definitions of normal functions go to the method implementation file.

Now we put definitions of all normal (non inline) functions into the file movingobject.cpp and do not forget to include the header file using the #include directive to let the compiler know where to look for the definition of the class:

#include "movingobject.h"

MovingObject::MovingObject()
{
  x = y = 0;
  speed = 1;
  dir = South;
}

void MovingObject::step()
{
  switch( dir ){
    case North: y += speed; break;
    case West:  x -= speed; break;
    case South: y -= speed; break;
    case East:  x += speed; break;
  }
}
File movingobject.cpp
Please note that in the #include directive we used double quotes instead of less than, greater than braces. We did so to let the compiler know that it needs to looks for the header file in the local folder, not in the standard 'include' folder.

Constructors and Destructor

C++ provides two special kind of member functions known as constructors and destructors. These methods are different from other class methods:
  1. Constructors and destructors cannot return any value. We don't even have to put the keyword void in front of their definitions.
  2. Constructors must have the same name as the name of the class. For example, for the class MovingObject the constructor must be MovingObject().
  3. Destructors must have the same name as the name of the class with tilde sign ~ in front of it. For the MovingObject class destructor will be ~MovingObject().
  4. We do not need to explicitly call the constructor method of a class. It will be invoked automatically at the moment when we define a variable of this class. Thus, for MovingObject class its constructor will be called when we put line
    MovingObject train;
  5. The destructor is called automatically when an object of the class passes out of scope.
  6. Destructor may not take any arguments.

To illustrate how these special methods work, let's modify the MovingObject class from example above by editing its constructor and adding a destructor:

public:
   ...
   MovingObject() { x=y=0; speed=1; dir=West;  cout<<"Constructor is working\n"; };
   ~MovingObject() { cout<<"Destructor is working\n"; };
   ...
Now, if we use the following main function
 1:   void main()
 2:   {
 3:      MovingObject car;
 4:      cout<<"We will start a nested block now\n---\n";
 5:      {
 6:         MovingObject boat;
 7:         cout<<"We are inside the nested block\n";
 8:      }
 9:      cout<<"---\nThe nested block is over now\n";
10:   }
we will see the following output
Constructor is working
We will start a nested block now
---
Constructor is working
We are inside the nested block
Destructor is working
---
The nested block is over now
Destructor is working
The first message "Constructor is working" corresponds to line 3 - we defined an object of the MovingObject class and by doing this we invoked the constructor. The second "Constructor is working" message corresponds to line 6 where we defined another object of the MovingObject class. The first message "Destructor is working" corresponds to line 8 where the scope of the variable boat ends. Since the scope ends, the destructor is automatically called. And finally, the last "Destructor is working" message is generated by line 10, where the scope of the variable car ends.

Since the name for the destructor is fixed and destructors may not take any arguments, a class can have only one destructor. However, one class can have a list of constructors that differ by number or type of parameters they take; that is, we can overload constructors just like any other method or function. A constructor that takes no arguments is called a default constructor. It's a very good idea always create a default constructor for a class. Constructors may take parameters. For example, we can describe a constructor for the MovingObject class that puts the object in a given point and another constructor that also sets the initial speed and direction:

class MovingObject {
   ...
   MovingObject();     // default constructor
   MovingObject(int X, int Y);
   MovingObject(int X, int Y, int s, Direction d);
   ...
};

...

MovingObject::MovingObject(int X, int Y)
{
   x = X;
   y = Y;
   speed = 1;
   dir = South;
}

MovingObject::MovingObject(int X, int Y, int s, Direction d)
{
   x = X;
   y = Y;
   speed = (s>=0) ? s : 1;
   dir = d;
}
...
To pass arguments to the constructor, we need to put these arguments inside the parenthesis in the declaration statement:
MovingObject robot(10, 10, 2, MovingObject::South);

The copy constructor and assignment operator

When an object is declared, it can be initialized so that it becomes a copy of another object of the same type. For example, we can create two identical objects of the type MovingObject using the following lines of code:
MovingObject car(10, 10, 2, MovingObject::South);
MovingObject track = car;
By default the assignment operator creates an exact copy of all attributes of the object. For example, after executing the lines from the example above, object track will also be located at the square with coordinates (10, 10), will have speed 2, and will be heading South.

It seems like exactly what we need, but now let's take a look what happens when we assign one TString object defined as

class TString {
   public:
      TString();
      TString(char *s);
      void Print();
      void Set(char *s);
   private:
      char *str;
      int   size;
      int   length;
};
to another. Assume that originally the first TString object str1 contains the string "Hello, world!"; that is, it actually contains a pointer to the memory allocated somewhere else, the length 13, and the amount of bytes allocated (let's say 16):
(The sea green cells are the bytes allocated for this object.) If we assign this object to another TString object:
TString str2 = str1;
then all the fields will be copied. That is, the new picture will look like:
First of all, the old values stored in the str2 object are lost after the assignment; that is, we allocated memory for this object somewhere and now the pointer to this memory is lost, so we cannot free it (note that the default assignment operator doesn't do this for us). Besides, we did not allocate the new memory for this object, after the assignment these two objects will use the same memory. Which, in particular means, after putting the new value in either one of the objects
str1.Set("Bye, world!");
the second object will "contain" the same value:
Please note, they although both objects refer to the same memory, the length value was updated only in the object str1 because this object invoked the Set() method.

The following example contains the code that illustrates the discussed issue.

To avoid such a situation, we can define the copy constructor. The copy constructor is a constructor that takes an object of the same type as the argument. In fact, this constructor should take a reference to the copying object. For example, the copy constructor for the TString class can de defined as

TString::TString(const TString &s)
{
   size = s.size;
   length = s.length;
   str = new char[size];
   strcpy(str, s.str);
}
If such a constructor is defined, then the line
TString str2 = str1;
will call str2.TString(str1). Thus, the picture after the initialization of the second TString object will look like
Please take a look at the modification of the previous example where we defined the copy constructor.

We would like to note one very important detail: the copy constructor can be called only with initialization and not with ordinary assignments. If we write, for example,

TString str2 = str1; // The copy constructor is called
str2 = str1;         // The assignment operator is called
the copy constructor will be called for in the first line but not in the second. Instead, the assignment operator will be called in the second line.

In fact, the default assignment operator does simple copying of all the attributes. On the example page you can find the example that illustrates this. In other words, to avoid the situation described above, we need to redefine the assignment operator.

C++ allows us to do that. From C++ point of view an operator is just a function with special syntax and parameters. In particular, the assignment operator takes only one argument (the one of the right hand side of the equal sign). That is, the notation str1 = str2 is very similar (from the compiler point of view) to

str1.Use_Assignment_Operator(str2);
The syntax to redefine this operator is:
class TString {
   public:
      ...
      const TString& operator = ( const TString &str );
      ...
};
As you can see, it looks like we define a new method with a strange name operator =. This method takes one argument of the TString type and returns a value of the same type. Strictly speaking, it returns a value of the TString& type; that is, it returns a modifiable value, but due to the modifier const, which appears before the type, we prohibit to change this value. This was done to prevent the method from creating a temporarily copy of the whole object and putting this object to the stack to return to the calling line. This may cause an additional problem because the destructor will be called for that stack copy when it's no longer needed. Shortly, this is a nice way to declare that we return an object of the TString type. Note: we need to do all of these things because our object does not contain all the data. It contains a pointer and some important data is stored at the location pointed by the pointer.
Technically speaking, the assignment operator doesn't have to return any value, but traditionally it returns the value of the object type. It is done to b able to use more than one assignment operator in one statement. For example, a = b = c;. In this statement the assignment operator b=c will be executed first, and then its value will be assigned to a by the next operator.
The implementation of the operator can look like
const TString& TString::operator = (const TString &s)
{
  delete[] str;
  str = new char[s.size];
  strcpy(str, s.string);
  length = s.length;
  size = s.size;
  return *this;
}
(Please find the complete example on the example page.) We have seen code like this several times, but there is one new detail. In the very last line of the function we use new keyword this. Before explaining this new keyword, let us ask a question: How can we return the current object? Note that we want to return the whole object not just a part of it (like size, length) and not a copy of the object. To do things like this, C++ provides a special predefined pointer this, which is always points to the current object. Since we as using the pointer inside a class method, the this pointer points to the whole object invoking the method (in case a = b it's the object a). We also need to use the star operator to return the object itself not a pointer to the object.

More about constructors **

C++ provides a slightly different syntax for defining constructors. Instead of carrying out the assignment in the function body, we can have initialization list in constructors, where we indicate what initialization value each data member is to have. For example, the constructors of the MovingObject class can be written as
MovingObject::MovingObject(int X, int Y) : x(X), y(Y), speed(1), dir(South) { }

MovingObject::MovingObject(int X, int Y, int s, Direction d) :  x(X), y(Y)
{
   speed = (s>=0) ? s : 1;
   dir = d;
}
Please note that despite the moving all initialization from the function body to the initialization list the method still has the body, but it is empty.

We also would like to notice that we don't have to move all initializations to the list. The second constructor has coordinates x and y initialized in the list and two other attributes are initialized in the body. In this constructor this doesn't matter if we use initialization list or carry out ordinary assignments; but sometimes it is necessary to use an initialization list, for instance if we have to initialize data members that are constants or references, since we are not allowed to assign to these. Moreover, initialization list can give more effective code when we initialize data members that are objects. It is therefore advisable to use initialization lists if possible.

Static members and static methods **

Sometimes you want to have one variable that is shared by all objects of a class. For example, you might want one variable to count the number of times a particular member function is invoked by all objects of the class. Such variables are called static variables and can be used for objects of the class to communicate with each other or coordinate their actions. Such variables allow some of the advantages of global variables without opening the flood gates to all the abuses that true global variables invite. In particular, a static variable can be private so that only objects of the class can directly access it.

If a function does not access the data of any object and yet you want the function to be a member of the class, you can make it a static function. Static functions can be invoked in the normal way, using a calling object of the class. However, it is more common and clearer to invoke a static function using the class name and scope resolution operator, as in the following example:

Robot::TotalHPLeft()

Because a static function does not need a calling object, the definition of a static function cannot use anything that depends on a calling object. A static function definition cannot use any nonstatic variables or nonstatic member functions, unless the nonstatic variable or function has a calling object that is a local variable or some object otherwise created in the definition. If that last sentence seems hard to understand, just use the simple rule that the definition of a static function cannot use anything that depends on a calling object.

To define a static data member or a method all you need to do is to put the keyword static in front of the definition. Please note, though, that every static variable must be initialized outside of the class definition. Also, a static variable cannot be initialized more than once. For example, if in a new class Robot we define a private static variable TotalHP, then it should be initialized as follows

class Robot {
   public:
      static int getTotalHP() { return totalHP; };
   private:
      static int TotalHP;
};

int Robot::TotalHP = 10;
This may seem to be contrary to the notion of private. However, the author of the class is expected to do the initialization, typically in the same file as the class implementation. In that case, no programmer who uses the class can initialize the static variable, since a static variable cannot be initialized a second time.

The robot program example illustrates the idea of static data members and methods creating a class Robot. Each object of the type Robot has a random numbers of hit points, but the total sum of the initial hit points cannot be greater than the value of shared variable TotalHP, which is initialized by 30. In the main function we start creating an array of robots and we stop when there is no hit points left to give to the next robot. Please also note that the destructor if the class Robot returns the initial amount of hit points given to a robot when a robot dies.

Friends

We have discussed the importance of hiding information inside classes such that only the members of the class will be able to see that information. Whatever is placed in the private section of a class definition is available only to members of the class. However, there are situations when we need to provide an access to the hidden data to functions non-members of the class. This is because the member functions in certain situations server to limit the various possibilities. C++ allows us to declare a function as a friend of a class. This, of course, should be done inside the declaration of the class, so that doesn't contradict the idea of data hiding. A friend function is allowed to view a class in its entirety.

Let us illustrate the syntax of creating a friend function by an example. Suppose we need to write a function that takes an array of instances of the MovingObject class and returns the index of the fastest object. We would not like to make this function to be a member of the MovingObject class, but it still needs to have an access to the private attribute speed. Of course, we could write a method getSpeed(), but it would be faster to access the variable directly, than to call a special function. Thus, to develop a function FindFatsest() we need to provide the access to private attributes of the class. To do this, we will insert the declaration of the function with the keyword friend inside the class definition and then write the function as usual C++ function:

class MovingObject {
   public:
     MovingObject();     // default constructor
     ...
     friend int FindFastest(int count, MovingObject *obj);
   ...
};

int FindFastest(int count, MovingObject *obj)
{
   int i, max_speed, ind;

   if( count < 0 ) return -1;
   max_speed = obj[0].speed;
   ind = 0;
   for(i=1;i<count;i++)
      if( max_speed < obj[i].speed ){
         max_speed = obj[i].speed;
     ind = i;
      }
   return ind;
}

Not only functions can be friends. Another class can be declared as a friend. In this case all of the methods of this friend class can access the private members of the other class. This is how we can make the class Board to be a friend of the MovingObject class:

class MovingObject {
   public:
     friend class Board;
   ...
};
Please note that friendship is a one-way relation in C++.
Try to explain how would it violate the idea of hiding data if it would be a two-way relation.


* This is a section 7.3.2 from C++ From the Beginning by Jan Skansholm.
** This is a section from chapter 7 of Absolute C++ by Walter Savitch.