Chapter III. Object-oriented programming in C++

Table of Contents
III.1. Introduction to the object-oriented world
III.1.1. Basics
III.1.2. Basic principles
III.1.2.1. Encapsulation, data hiding
III.1.2.2. Inheritance
III.1.2.3. Abstraction
III.1.2.4. Polymorphism
III.1.3. An object-oriented example code
III.2. Classes and objects
III.2.1. From structures to classes
III.2.1.1. A little revision
III.2.1.2. Grouping together data and operations
III.2.1.3. Data hiding
III.2.1.4. Constructors
III.2.1.4.1. Using member initialisation lists
III.2.1.4.2. Explicit initialisation of objects
III.2.1.5. Destructor
III.2.1.6. Objects of a class, the pointer this
III.2.2. More about classes
III.2.2.1. Static class members
III.2.2.2. How to structure classes
III.2.2.2.1. Implicit inline member functions
III.2.2.2.2. Class structures in C++/CLI applications
III.2.2.2.3. Storing member functions in separate modules
III.2.2.3. Friend functions and classes
III.2.2.4. What can we also add to classes?
III.2.2.4.1. Constant data members of objects
III.2.2.4.2. Reference type data members
III.2.2.4.3. Data members as objects
III.2.2.5. Pointers to class members
III.2.3. Operator overloading
III.2.3.1. Creating operator functions
III.2.3.2. Using type conversion operator functions
III.2.3.3. Extending classes with input/output operations
III.3. Inheritance (derivation)
III.3.1. Derivation of classes
III.3.2. Initialising base class(es)
III.3.3. Accessing class members in case of inheritance
III.3.3.1. Accessing inherited members
III.3.3.2. The friend relationship in inheritance
III.3.4. Virtual base classes in case of multiple inheritance
III.3.5. Inheritance and/or composition?
III.3.5.1. Reuse with composition
III.3.5.2. Reuse by public inheritance
III.4. Polymorphism
III.4.1. Virtual member functions
III.4.2. Redefining virtual functions
III.4.3. Early and late binding
III.4.3.1. Static early binding
III.4.3.2. Dynamic late binding
III.4.3.3. Virtual method table
III.4.4. Virtual destructors
III.4.5. Abstract classes and interfaces
III.4.6. Run-time type informations in case of classes
III.5. Class templates
III.5.1. A step-be-step tutorial for creating and using class templates
III.5.2. Defining a generic class
III.5.3. Instantiation and specialisation
III.5.4. Value parameters and default template parameters
III.5.5. The "friends" and static data members of a class template
III.5.6. The Standard Template Library (STL) of C++
III.5.6.1. The structure of STL
III.5.6.2. STL and C++ arrays
III.5.6.3. Using STL containers
III.5.6.4. Using STL container adaptors

Object-oriented programming (OOP) is a contemporary programming methodology (paradigm), which divides whole programs into closed program units (objects) that have individual features and that are able to function by themselves. Object-oriented programming offers a much more efficient solution to most problems than classic, structured programming and it can reduce the time necessary to develop software by supporting the creation and reuseability of objects doing abstract operations.

III.1. Introduction to the object-oriented world

Object-oriented programming uses the "things" ("objects") and the interaction between them to plan and to develop applications and computer programs. This methodology comprises solutions, like encapsulation, modularity, polymorphism and inheritance.

It should be noted that OOP languages only offer tools and support in order that object-orientation be realised. In the present book, we will also focus on presenting these tools after a short overview.

III.1.1. Basics

Now let's have a look at the basic notions of object-orientation. To understand these, we do not even have to have much knowledge in programming.

Class

A class determines the abstract features of a thing (object), including its features (attributes, fields, properties) and its behaviour (what the thing can do, methods, operations and functions).

We can say that a class is a scheme describing the nature of something. For example, the class Truck has to contain the common properties of trucks (manufacturer, engine, brake system, maximum load etc.), as well as their specific behaviour, like braking, turning to left etc.

Classes offer by themselves modularity and structuredness for object-oriented computer programs. The notion of class should also be understandable for those people who are familiar with the given problem but not familiar with programming that is the features of a class should be expressive. The code of a class should be relatively independent (encapsulation). Both the integrated properties and the methods of a class are called class members (data member and member function in C++).

Object

An object is a scheme (an example) of a class. With the help of the class Truck, we can define all possible trucks by enumerating their properties and behaviour forms. For example, the class Truck has the feature brake system, but the brake system of the object myCar can be an electronically controlled one (EBS) or a simple air brake.

Instance

The notion of instance (of a class) is synonymous with the notion of object. Instance means an actual object created at runtime. So, myCar is an instance of the class Truck. The set of the property values of the actual object is called the state of that object. Therefore all objects are characterised by the state and behaviour defined in their corresponding class.

Method

Methods are responsible for the capabilities of objects. In spoken language, the corresponding term for methods is verbs. Since myCar is a Truck, it has the ability of braking, so Brake() is one of the methods of myCar. Of course, it may have other methods as well, like Ignition(), StepOnTheGas(), TurnLeft() or TurnRight(). Within a program, a method has only effect on the given object in general when it is used. Although all trucks can brake, activating (calling) the method Brake() will only slow down one given vehicle. In C++, methods are rather called member functions.

The object myCar (an instance of the class Truck)
Figure III.1. The object myCar (an instance of the class Truck)


Message passing

Message passing is the process during which an object sends data to another object or "asks" another object to execute one of its methods. The role of message passing can be better understood if we think of the simulation of the functioning of trucks. In that simulation, the object driver sends a "brake" message to activate the Brake() method of myCar, which results in braking the vehicle. The syntax of message passing may be very different among different programming languages. On the code level, message passing is realised by calling a method in C++.

III.1.2. Basic principles

The object-orientation of a system, of a programming language can be measured by supporting the following principles. If it is only some of these principles that are realised, that is an object-based system, if all four principles are supported, that is an object-oriented system.

III.1.2.1. Encapsulation, data hiding

As we have already seen, classes principally consist of features (state) and methods (behaviour). However, the state and behaviour of objects can be divided into two groups. There are some features and methods that we hide from other objects. These are internal (private or protected) states and behaviour. However, the others are made public. According to the basic principles of OOP, the state features have to be private while most of the methods may be public. If it is needed, public methods can be used to access private features in a controlled way.

It is not necessary for an object that passes a message to another object to know the inner structure of the latter. For example, the Truck has the method Brake(), which exactly defines how braking takes place. However, the driver of myCar does not have to know how that car brakes.

All objects provide a well-defined interface for the external world. That interface defines what can be accessed from the given object from the outside. If this interface is completely written, there is not any problem to modify the internal world of the class in the future for client applications using that object. For example, it can be ensured that trailers may only be connected to objects of the class HeavyTruck.

III.1.2.2. Inheritance

Inheritance means creating specific versions of a class that inherit the features and behaviour of their parent class (base class) and use them as if they were of their own. The classes created in this way are called subclasses or derived classes.

For example, the subclasses Van and HeavyTruck are derived from the class Truck. In the following, myCar will be an instance of the class HeavyTruck. Let's also suppose that the class Truck defines the method Brake() and the property brake system. All classes derived from that class (Van and HeavyTruck) inherit these members, so programmers have to write only once the code corresponding to them.

Inheritance
Figure III.2. Inheritance


Subclasses may change the inherited properties. For example, the class Van can prescribe that its maximum load is 20 tons. The subclass HeavyTruck may make EBS braking as its default for its method Brake().

Derived classes can be extended with new members as well. The method Navigate() can be added to the class HeavyTruck. On the basis of what has been said so far, the method Brake() of the given instance HeavyTruck uses an EBS-based brake in spite of the fact that it inherits a traditional Brake() method from the class Truck; it also has a new method named Navigate(), which cannot be found in the class Van.

Actually, inheritance is an is-a relation: myCar is a HeavyTruck, a HeavyTruck is a Truck. So myCar has the methods of both HeavyTruck and Truck.

Both derived classes have one direct parent class, namely Truck. This inheritance method is called single inheritance to be differentiated from multiple inheritance.

Multiple inheritance means that a derived class inherits the members of more direct parent classes. For example, we can define two classes totally independent of each other with the names Truck and Ship. From these classes, we can create a new class named Amphibious, which has the features and behaviour of both trucks and ships. Most programming languages (ObjectPascal, Java, C#) support only single inheritance; however, C++ offers both inheritance methods.

Multiple inheritance
Figure III.3. Multiple inheritance


III.1.2.3. Abstraction

Abstraction simplifies complex reality by modelling problems with their corresponding classes and it has its effects on the level of inheritance appropriate for these problems. For example, myCar can be treated as a Truck in most cases; however, it may also be a HeavyTruck as well if the specific features and behaviour of HeavyTruck are needed but it can also be considered as a Vehicle if it is treated as a member of a fleet of vehicles. (Vehicle is the parent class of Truck in the example.)

Abstraction can be achieved through composition. For example, a class named Car has to contain the components engine, gearbox, steering gear and many others. In order to construct a Car, we do not have to know how the different components work, we only have to know how to connect to them (i.e. its interface). An interface determines how to send them or receive from them messages and it gives information about the interaction between the components of the class.

III.1.2.4. Polymorphism

Polymorphism makes it possible to replace the content of some inherited (deprecated) behaviour forms (methods) with a new one in the derived class and to treat the new, replaced methods as the members of the parent class.

For the next example, let's suppose that the classes Truck and Bicycle inherit the method Accelerate() of the class Vehicle. In the case of a Truck, the command Accelerate() means the operation StepOnTheGas(), whereas in the case of a Bicycle it means calling the method Pedal(). In order that acceleration function correctly, the method Accelerate() of the derived classes should override the method Accelerate() inherited from the class Vehicle. This is overriding polymorphism.

Most OOP languages support parametric polymorphism as well where methods are written for compilers as schemes independently of types. In C++, this can be carried out with templates.

III.1.3. An object-oriented example code

Finally, let's see a C++ code written on the basis of the things said so far. Now, we mainly aim at giving readers some impressions about OOP, since OOP is only detailed in the following subchapters of the present book.

#include <iostream>
#include <string>
using namespace std;
 
class Truck {
   protected:
      string manufacturer;
      string engine;
      string brake_system;
      string maximum_load;
   public:
      Truck(string ma, string en, string brake,
                double load) {
         manufacturer = ma;
         engine = en;
         brake_system = brake;
         maximum_load = load;
      }
      void StartUp() { }
      void StepOnTheGas() { }
      virtual void Brake() {
           cout<<"Classic braking."<< endl;
      }
      void TurnLeft() { }
      void TurnRight() { }
};
 
class Van : public Truck {
    public:
      Van(string ma, string en, string brake)
            : Truck(ma, en, brake, 20) {  }
};
 
class HeavyTruck : public Truck {
    public:
      HeavyTruck(string ma, string en, string brake, double load) 
            : Truck(ma, en, brake, load) {  }
      void Brake() { cout<<"Brake with EBS."<< endl; }
      void Navigate() {}
};
 
int main() {
    Van post("ZIL", "Diesel", "airbrake");
    post.Brake();      // Classic braking.
    HeavyTruck myCar("Kamaz", "gas engine", "EBS", 40);
    myCar.Brake();    // Brake with EBS.
}

The following chapters of this book presents the C++ tools that realise the solutions related to the notions presented above. However, this overview is not enough for acquiring a routine in OOP, it only helps getting started.

III.2. Classes and objects

In C++ language, there are two possibilities to create an ADT – abstract data type of the object-oriented framework. The type struct of C++ is an extension to the structure type of C language and as such, it has become able to define abstract data types. C++ also offers us the new class type.

The types struct and class are built up of data members and related operations (member functions). Classes can be created by both data types; however, it is the class type that observes more the principles of obejct- orientation because of the default access restriction of its members. By default, all members of a type struct is public, whereas the members of a type class can only be accessed by the member functions of that class.

A class declaration has two parts. The header of the class contains the keyword class/struct, followed by the name of the class. The header is followed by the class body, which is enclosed within curly brackets followed by a semi-colon. Declarations contain not only data members and member functions but also keywords regulating access to the members and followed by a colon: public, private (hidden) and protected.

class ClassName {
  public:
   type4 Function1(parameterlist1) {  }
   type5 Function2(parameterlist2) {  }
  protected:
    type3 data3;
  private:
    type1 data11, data12;
    type2 data2;
};

The declaration of class and struct classes can be placed anywhere in a C++ code where a declaration is allowed; however what comply the most with contemporary software development methods is a declaration on a module (file) level.

III.2.1. From structures to classes

In this chapter, based on our previous knowledge about the type struct, we will learn how to create and use objects. The problems raised in the subchapters are first solved in a conventional way than in an object-oriented aspect.

III.2.1.1. A little revision

We have already learnt how to group the data necessary to solve a problem with a structure type if the data are to be stored in the members of that structure:

struct Employee{
    int employeeID;
    string name;
    float salary;
};

We are also able to define operations with functions that get as an argument the structure type variable:

void IncreaseSalary(Employee& a, float percent) {
    a.salary *= (1 + percent/100);
}

The members of the structure can be accessed by the dot or arrow operator depending on who we entrust with memory allocation: ourselves or compilers:

int main() {
    Employee engineer;
    engineer.employeeID = 1234;
    engineer.name = "Tony Clever";
    engineer.salary = 2e5;
    IncreaseSalary(engineer,12);
    cout << engineer.salary << endl;
 
    Employee *pAccountant = new Employee;
    pAccountant->employeeID = 1235;
    pAccountant->name = "Sarah Rich";
    pAccountant->salary = 3e5;
    IncreaseSalary(*pAccountant,10);
    cout << pAccountant->salary << endl;
    delete pAccountant;
}

Of course, structured and efficient program codes can be created in this way too; however, in this chapter, we intend to go further.

III.2.1.2. Grouping together data and operations

As a first step, relying on the basis of the principle named encapsulation, we group together data and operations to be done on them in one code unit, which we call class.

struct Employee {
    int employeeID;
    string name;
    float salary;
    void IncreaseSalary(float percent) {
        salary *= (1 + percent/100);
    }
};

It can be seen at first glance, that the function IncreaseSalary() does not receive the class type variable (object) as a parameter, since it carries out an operation on the object by default. The main () function that demonstrates the usage of the objects of type Employee, since we only call now the member function corresponding to the variable:

int main() {
    Employee engineer;
    engineer.employeeID = 1234;
    engineer.name = "Tony Clever";
    engineer.salary = 2e5;
    engineer.IncreaseSalary(12);
    cout << engineer.salary << endl;
 
    Employee *pAccountant = new Employee;
    pAccountant->employeeID = 1235;
    pAccountant->name = "Sarah Rich";
    pAccountant->salary = 3e5;
    pAccountant->IncreaseSalary(10);
    cout << pAccountant->salary << endl;
    delete pAccountant;
}

III.2.1.3. Data hiding

Accessing class type variables (objects) directly contradicts the principle of data hiding. In object-oriented programming it is required that the data members of classes could not be accessed directly from the outside. The type struct offers complete access to its members by default, whereas the class type completely hides its members from the outside, which complies much more with the principles of OOP. It should be noted that the access of class elements can be defined by programmers as well with the keywords private, protected and public.

public members can be accessed anywhere within the code where the object itself is accessible. On the contrary, private members can be accessed only from the the member functions of their class. (The access type protected will be applied in the case of inheritance treated in Chapter Section III.3

Within a class, a member group of any number of elements can be created with the usage of the keywords (private, protected, public) and there is no restriction on the order of groups.

If we stick to the previous example, it is necessary to write further member functions with which we can set and get in a controlled way the value of data members because of their restricted access modes. The setting functions may also check that only valid data could appear in the object of type Employee. Getting functions are often defined as a constant, which means that the value of data members cannot be modified with that function. In a constant member function, the reserved word const is put between the header and the body of that function. In our example, GetSalary() is a constant member function.

class Employee{
  private:
    int employeeID;
    string name;
    float salary;
  public:
    void IncreaseSalary(float percent) {
        salary *= (1 + percent/100);
    }
    void SetData(int code, string n, float s) {
        employeeID = code;
        name = n;
        salary = s;
    }
    float GetSalary() const {
        return salary;
    }
};
 
int main() {
    Employee engineer;
    engineer.SetData(1234, "Tony Clever", 2e5);
    engineer.IncreaseSalary(12);
    cout << engineer.GetSalary() << endl;
 
    Employee *pAccountant = new Employee;
    pAccountant->SetData(1235, "Sarah Rich", 3e5);
    pAccountant->IncreaseSalary(10);
    cout << pAccountant->GetSalary() << endl;
    delete pAccountant;
}

It should be noted that data members can be changed with the help of constant member functions, too, in case they are declared with the keyword mutable, for example:

mutable float salary;

However, these solutions are rarely used.

It should be noted that if all data members of a class have public access, the object can be initialised by the solution already presented in the case of structures, for example:

Employee doorman = {1122, "John Secure", 1e5};

Since the useability of the formula above will be later restricted by other constraints as well (it should not be a derived class, it cannot have virtual member functions), it is recommended that they be initialised by the special member functions of classes, namely by the so-called constructors.

III.2.1.4. Constructors

In program codes using classes, one of the most frequent operations is creating objects. Some objects are created by programmers themselves by static or dynamic memory allocation (see example above); however, there are cases when the compiler creates so-called temporary object instances. How can we assign the data members of objects to be created an initial value? By data members that are called constructors.

class Employee{
  private:
    int employeeID;
    string name;
    float salary;
  public:
    Employee() {                           // default
        employeeID = 0;
        name = "";
        salary = 0;
    }
    Employee(int code, string n, float s) { // by parameters
        employeeID = code;
        name = n;
        salary = s;
    }
    Employee(const Employee & a) {      // by copying values 
        employeeID = a.employeeID;
        name = a.name;
        salary = a.salary;
    }
    void IncreaseSalary(float percent) {
        salary *= (1 + percent/100);
    }
    void SetName(string n) {
        name = n;
    }
    float GetSalary() const {
        return salary;
    }
};
 
int main() {
    Employee employee;
    employee.SetName("Stephen Smith");
 
    Employee engineer(1234, "Tony Clever", 2e5);
    engineer.IncreaseSalary(12);
    cout << engineer.GetSalary() << endl;
 
    Employee firstEngineer = engineer;
    // or: Employee firstEngineer(engineer);
    firstEngineer.IncreaseSalary(50);
    cout << firstEngineer.GetSalary() << endl;
 
    Employee *pEmployee = new Employee;
    pEmployee->SetName("Stephen Smith");
    delete pEmployee;
 
    Employee *pAccountant;
    pAccountant = new Employee(1235, "Sarah Rich", 3e5);
    pAccountant->IncreaseSalary(10);
    cout << pAccountant->GetSalary() << endl;
    delete pAccountant;
 
    Employee *pFirstEngineer=new Employee(engineer);
    pFirstEngineer->IncreaseSalary(50);
    cout << pFirstEngineer->GetSalary() << endl;
    delete pFirstEngineer;
}

In the example above, we created a constructor without parameters, a constructor with parameters and a copy constructor, making use of function name overloading. Thus, a constructor is a member function the name of which corresponds to the name of the class and has no return type. The constructor of a class is called automatically by compilers every time an object of the given class is created. A constructor has no return value. Except from that, it behaves like any other member function. With redefined (overloaded) constructors, an object can be initialised in many ways.

A constructor does not allocate memory space for the object to be created, it only has to initialise the memory space already allocated for it. However, if the object contains a pointer, then the constructor has to ensure that a memory space to which the pointer points is allocated.

A class has two constructors by default: a constructor without parameters (default) and a copy constructor. If we create a personalised constructor, then the default constructor is not available any more, so it has to be defined as well. We generally use our own copy constructor if a dynamic memory space is associated with the instances of a class.

Constructors with and without parameters are often contracted by introducing default arguments:

class Employee{
  private:
    int employeeID;
    string name;
    float salary;
  public:
    Employee(int code = 0, string n ="", float s=0) { 
        employeeID = code;
        name = n;
        salary = s;
    }
    …
}
III.2.1.4.1. Using member initialisation lists

The members of a class can be assigned a value in two different ways from a constructor. We have already mentioned the solution consisting of assigning values within the body of a constructor. Besides that, there is another possibility in C++, namely member initialisation lists. An initialisation list is provided after a colon after the header of a constructor. The elements, separated from each other by a comma, are the data members of the class, and they are followed by their initial value enclosed within parentheses. If member initialisation lists are used, the constructors of the example above become empty:

class Employee{
  private:
    int employeeID;
    string name;
    float salary;
  public:
    Employee(int code=0, string n="", float s=0) 
        : employeeID(code), name(n), salary(s) { }
    Employee(const Employee & a)
        : employeeID(a.employeeID), name (a.name), 
          salary(a.salary) { }
    …
}

It should be noted that when a constructor is called, it is processing the initialisation list that takes place first, and the execution of the body of the constructor only after.

III.2.1.4.2. Explicit initialisation of objects

In the case of constructors having only one parameter, compilers carry out implicit type conversion, if needed, in order to choose the appropriate constructor. If the keyword explicit is used before a constructor, conversion can be hindered after the constructor is called.

In the following example, we differentiate between the two types of initial value assignment (explicit and implicit):

class Number 
{
  private:
    int n;
  public:
    explicit Number( int x) {
        n = x;
        cout << "int: " << n << endl;
    }
    Number( float x) {
        n = x < 0 ? int(x-0.5) : int(x+0.5);
        cout << "float: " << n << endl;
    }
};

int main() {
  Number a(123);    // explicit call
  Number b = 123;   // implicit (not explicit) call
}

When the object a is created, it is the explicit constructor that is called, whereas in the case of the object b, it is the constructor with the parameter of type float. If the keyword explicit is missing, it is the first constructor that is activated in both cases.

III.2.1.5. Destructor

The resources (memory, files, etc.) allocated often during the object creation have to be freed, when the object is destroying. Otherwise, these resources will become unavailable for the program.

For that purpose, C++ offers a special member function, the destructor, in which we can free the allocated resources. The name of a destructor has to be provided as a class name with the tidle character (~). A destructor, just like constructors, does not return any value.

In the following example, a dynamically allocated array with 12 elements is created in the constructors in order that the work hours done by employees could be stored for every month. The memory allocated for the array is freed in the destructor.

class Employee{
  private:
    int employeeID;
    string name;
    float salary;
    int *pWorkhours;
  public:
    Employee(int code = 0, string n ="", float s=0) { 
        employeeID = code;
        name = n;
        salary = s;
        pWorkhours = new int[12];
        for (int i=0; i<12; i++) pWorkhours[i]=0;
    }
    Employee(const Employee & a) {
        employeeID = a.employeeID;
        name = a.name;
        salary = a.salary;
        pWorkhours = new int[12];
        for (int i=0; i<12; i++) 
            pWorkhours[i]=a.pWorkhours[i];
    }
    ~Employee() {
        delete[] pWorkhours;
        cout << name << " deleted" << endl;
    }
    void IncreaseSalary(float percent) {
        salary *= (1 + percent/100);
    }
    void SetWorkhours(int month, int hours) {
        if (month >= 1 && month <=12) {
            pWorkhours[month-1]=hours;
        }
    }
    float GetSalary() const {
        return salary;
    }
};
 
int main() {
    Employee engineer(1234, "Tony Clever", 2e5);
    engineer.IncreaseSalary(12);
    engineer.SetWorkhours(3,192);
    cout << engineer.GetSalary() << endl;
 
    Employee *pAccountant;
    pAccountant = new Employee(1235, "Sarah Rich", 3e5);
    pAccountant->IncreaseSalary(10);
    pAccountant->SetWorkhours(1,160);
    pAccountant->SetWorkhours(12,140);
    cout << pAccountant->GetSalary() << endl;
    delete pAccountant;
}

Compilers call the destructor of a class every time when the object is not valid any more. An exception to that rule is constituted by dynamic objects created by the operator new, the destructor of which can be activated only by the operator delete. It should be noted that a destructor does not delete the object itself but automatically do some "cleaning" tasks that is told to it.

When this example code is executed, the following text is printed out:

224000
330000
Sarah Rich deleted
Tony Clever deleted

So, it can be clearly seen that first it is the destructor of the object *pAccountant that is called when the operator delete is used. Then, when the closing curly bracket of the body of the main () function has been reached, the destructor of the object engineer is automatically called.

If a destructor is not written for a class, the compiler automatically adds an empty destructor for that class.

III.2.1.6. Objects of a class, the pointer this

If objects (variables of type class) of the class type Employee are created:

Employee engineer(1234, "Tony Clever", 2e5);
Employee *pAccountant;
pAccountant = new Employee(1235, "Sarah Rich", 3e5);

each object has its own data members; however, these objects share only one instance of member functions (Figure III.4).

engineer.IncreaseSalary(12);
pAccountant->IncreaseSalary(10);

Then how the function IncreaseSalary() know that when it is called which memory space should be accessed?

The answer to this question is provided by an invisible activity of compilers: each member function, even those without a parameter, has an invisible parameter (this) in which a pointer to the actual object is passed to the function when it is called. All references to data members are inserted in a program code automatically in the following way:

this->datamember

The class Employee and its objects
Figure III.4. The class Employee and its objects


Programmers may also use the pointer this within member functions. This is a good solution to handle ambiguities when the name of a parameter equals with that of a data member:

class Employee{
  private:
    int employeeID;
    string name;
    float salary;
  public:
    Employee(int employeeID=0, string name="", float salary=0){
        this->employeeID = employeeID;
        this->name = name;
        this->salary = salary;
    }
};

In normal member functions, the pointer this is declared as Classtype* const this, and in constant member functions as const Classtype*const this.

III.2.2. More about classes

In the previous subchapter, we arrived to classes from structures. We learned how to create well useable classes. Now, it is time to solve tasks with the help of classes.

The present subchapter deals with less general knowledge about classes. This subchapter is mainly dedicated to those who already has some practice in how to create classes.

III.2.2.1. Static class members

In C++, the keyword static can be typed before the data members of classes in order that the objects of these classes could share these members (just like member functions). A static data member that is created in only one instance belongs directly to the class; therefore it is available for it even if there are no objects for that class.

A static data member should not be initialised within its class (independently of its access restriction). An exception to that rule is constituted by data members of type static const integer and enumeration to which a value can be assigned even within the class.

If a static data member is public, then it can be used anywhere in the program code by the name of the class and the scope operator (::). Otherwise, only instances of the class can access these members.

Besides presenting the usage of static members, the following example also demonstrates how to insert constants (static const and enum) in classes. Our mathematics class defined by ourselves (Math) makes it possible for us to call Sin() and Cos() member functions with data in radians or in degrees:

#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
 
class Math {
  public:
    enum Unit {degree, radian};
  private:
    static double dDegree2Radian;
    static Unit eMode;
  public:
    static const double Pi;
 
    static double Sin(double x)
           {return sin(eMode == radian ? x : dDegree2Radian*x);}
    static double Cos(double x)
           {return cos(eMode == radian ? x : dDegree2Radian*x);}
static void MeasureUnit(Unit mode = radian) { 
                                          eMode = mode; }
    void PrintOutPI() { cout.precision(18); cout<<Pi<<endl;}
};
 
// creating and initialising static members
const double Math::Pi = 3.141592653589793238462;
double Math::dDegree2Radian = Math::Pi/180;
Math::Unit Math::eMode = Math::radian;

As it can be seen in the example, an enumeration can also be placed within a class. The type name Unit and the enumerated constants (degree, radian) can be referenced with a name qualified with the class name. These names have class level scope, independently of the type name preceded by the keyword enum (Math::Unit): Math::radian, Math::degree.

To manage static data members, we use static member functions in general (Math::Sin(), Math::Cos(), Math::MeasureUnit()). However, normal data members cannot be accessed from static functions since the pointer this does not figure among their parameters. The static members of a class can be accessed from non-static member functions without restrictions.

A possible usage of the class Math is presented in the following example:

int main() {
   double y = Math::Sin(Math::Pi/6);    // counts in radian
   Math::MeasureUnit(Math::degree);     // counts in degrees
   y = Math::Sin(30);
   Math::MeasureUnit(Math::radian);  // counts in radian
   y = Math::Sin(Math::Pi/6);
 
   Math m;                            // class instance
   m.MeasureUnit(Math::degree);       // or
   m.MeasureUnit(m.degree);
   y = m.Sin(30);
   m.MeasureUnit(m.radian);           // or
   m.MeasureUnit(Math::radian);
   y = m.Sin(Math::Pi/6);
   m.PrintOutPI();
}

III.2.2.2. How to structure classes

The rules of C++ make it possible to structure classes in many ways. In the following examples, each of these methods is strictly separated but in the everyday practice we use them in different combinations.

III.2.2.2.1. Implicit inline member functions

In the first subchapter, the whole definition of member functions is placed in the description of a class. Compilers consider these member functions automatically as inline functions. A big advantage of the solution is that the whole class can be stored in a header file, and the members of the class are logically grouped together. In general, this solution is useful for small-sized classes.

As an example, let's see the class Point, which is able to handle points in the plane.

class Point {
   private:
      int x,y;
   public:
      Point(int a = 0, int b = 0) { x = a; y = b; }
      int GetX() const { return x; }
      int GetY() const { return y; }
      void SetX(int a) { x = a; }
      void SetY(int a) { y = a; }
      void Move(int a, int b) { x = a; y = b; }
      void Move(const Point& p) { x = p.x; y = p.y; }
      void PrintOut() const { cout<<"("<<x<<","<<y<<")\n"; }
};
III.2.2.2.2. Class structures in C++/CLI applications

The .NET project of Visual C++, Java and C# languages follow principles similar to those above. But there is a big difference: class members are not grouped on the basis of their accessibility, so the accessibility can be provided to each member separately.

class Point {
   private: int x,y;
   public: Point(int a = 0, int b = 0) { x = a; y = b; }
   public: int GetX() const { return x; }
   public: int GetY() const { return y; }
   public: void SetX(int a) { x = a; }
   public: void SetY(int a) { y = a; }
   public: void Move(int a, int b) { x = a; y = b; }
   public: void Move(const Point& p) { x = p.x; y = p.y; }
   public: void PrintOut() const { cout<<"("<<x<<","<<y<<")\n"; }
};
III.2.2.2.3. Storing member functions in separate modules

It is easier to manage a bigger-sized class if its member functions are stored in a separate C++ module. Then the declaration of the class contains not only data members but also the prototype of member functions. The header file containing the description (.H) and the module storing the definition of member functions (.CPP) have the same name in general and refer to their corresponding class.

The description of the class Point in the Point.h header file:

#ifndef __PONT_H__
#define __PONT_H__
 class Point {
   private:
      int x,y;
   public:
      Point(int a = 0, int b = 0);
      int GetX() const;
      int GetY() const;
      void SetX(int a);
      void SetY(int a);
      void Move(int a, int b);
      void Move(const Point& p);
      void PrintOut() const;
 };
#endif

The name of member functions has to be qualified with the name of the class (::) in the file Point.cpp:

#include <iostream>
using namespace std;
#include "Point.h"
 
Point::Point(int a, int b) { 
   x = a; y = b;
}            
 
int Point::GetX() const {
   return x; 
} 
 
int Point::GetY() const {
   return y; 
} 
 
void Point::SetX(int a) {
   x = a; 
}
 
void Point::SetY(int a) {
   y = a; 
}
 
void Point::Move(int a, int b) { 
   x = a; y = b; 
}            
 
void Point::Move(const Pont& p) {
   x = p.x; y = p.y; 
}
 
void Point::PrintOut() const {
   cout<<"("<<x<<","<<y<<")\n"; 
}

Of course, the keyword inline can be used explicitly for member functions; however, in that case, the definition of inline member functions has to be moved from the C++ module to the header file:

#ifndef __PONT_H__
#define __PONT_H__
class Point {
   private:
      int x,y;
   public:
      Point(int a = 0, int b = 0);
      int GetX() const;
      int GetY() const;
      void SetX(int a);
      void SetY(int a);
      void Move(int a, int b);
      void Move(const Point& p);
      inline void PrintOut() const;
};
 
void Point::PrintOut() const {
   cout<<"("<<x<<","<<y<<")\n";
}
 
#endif

III.2.2.3. Friend functions and classes

There are cases where the data hiding principles of C++ prevents us to write an efficient program code.  However, the friend mechanism makes it possible for us to access the private and protected members of a class from a function outside the class.

A friend declaration can be placed anywhere within the description of that class. An external function, a member function of another class and even a whole other class (that is all of its member functions) can be a "friend". Accordingly, a friend declaration consists of the prototype of the functions and the name of the class, the whole introduced by the keyword class.

It should be noted that a "friend" relationship is not mutual in the case of a friend class because it is only the member functions of the class figuring in the friend declaration that have full access to the members of the class containing that description.

In the following example, the external function Sum(), the public member function named Count() of BClass as well as all data functions of AClass have full access to all members of CClass:

class AClass;
 
class BClass {
 public:
  int Count(int x) { return x++; }
};
 
class CClass {
  friend long Sum(int a, int b);
  friend int BClass::Count(int x);
  friend class AClass;
  // ...
};
 
long Sum(int a, int b) {
  return long(a) + b;
}

For another example, let's see the simplified version of our class Point. Since the operation calculating the distance of points cannot be related to any points, a separate external function is written in order that the distance could be determined. The two points are passed as arguments to that function. In order that data members be quickly accessed, a direct access is needed, and this can be realised by the "friend" mechanism.

#include <iostream>
#include <cmath>
using namespace std;
 
class Point {
      friend double Distance(const Point & p1,
                             const Point & p2);
   private:
      int x,y;
   public:
      Point(int a = 0, int b = 0) { x = a; y = b; }
      int GetX() const { return x; }
      int GetY() const { return y; }
      void SetX(int a) { x = a; }
      void SetY(int a) { y = a; }
      void Move(int a, int b) { x = a; y = b; }
      void Move(const Point& p) { x = p.x; y = p.y; }
      void PrintOut() const { cout<<"("<<x<<","<<y<<")\n"; }
};
 
double Distance(const Point & p1, const Point & p2) {
  return sqrt(pow(p1.x-p2.x,2.0)+pow(p1.y-p2.y,2.0));
}
 
int main() {
  Point p, q;
  p.Move(1,2);
  q.Move(4,6);
  cout<<Distance(p,q)<<endl;
}

III.2.2.4. What can we also add to classes?

Until now, we have only placed data and functions in classes. We have also added static constants and the type enum to classes.

In the following parts, we will see how to put constants and references in classes, and after that we will also summarise the rules of using nested classes.

III.2.2.4.1. Constant data members of objects

There are cases where a unique constant value is needed to be assigned to object instances, for example a name or an identifier. It is possible if the the data member is preceded by the keyword const and if it is added to the member initialisation list of constructors.

In the following example, objects of type User are created and the public name of users are used as constants:

class User {
    string password;
 public:
    const string name;
    User(string user, string psw="") : name(user) {
         password=psw;
    }
    void SetPassword(string newpsw) { password = newpsw;}
};
 
int main() {
   User nata("Lafenita");
   User gardener("Liza");
   nata.SetPassword("Atinefal1223");
   gardener.SetPassword("Azil729");
   cout<<nata.name<<endl;
   cout<<gardener.name<<endl;
   User alias = nata;
   // alias = gardener;   // error!
}

It should be noted that the traditional member by member copy does not work between objects of the same type if these contain constant members.

III.2.2.4.2. Reference type data members

Since a reference can only be created for an already existing variable, the same is true for references like for constants when objects are constructed. In the following example, the sensor object is linked with the controller object by a reference:

class Sensor {
   private:
      int data;
   public:
     Sensor(int x) { data = x; }
     int Read() { return data; }
};
 
class Controller {
   private:
     Sensor& sensor;
   public:
     Controller(Sensor& s) : sensor(s) {}
     void ReceiveData() { cout<<sensor.Read(); }
};
 
int main() {
   Sensor speed(0x17);
   Controller ABS(speed);
   ABS.ReceiveData();
}
III.2.2.4.3. Data members as objects

It is frequent to place the object instance of a class in another class as a data member. It is an important rule that when objects of a class like this are created, the inner objects also have to be initialised, which can be achieved by adding them to the member initialisation list of the appropriate constructor call.

Calling a constructor is not necessary if the class of the member object has a constructor without a parameter (default constructor), which is automatically called.

The previous controller-sensor example code can be modified in order that the sensor appear as an object in the controller object:

class Sensor {
   private:
      int data;
   public:
     Sensor(int x) { data = x; }
     int Read() { return data; }
};
 
class Controller {
   private:
     Sensor sensor;
   public:
     Controller() : sensor(0x17) {}
     void ReceiveData() { cout<<sensor.Read(); }
};
 
int main() {
   Controller ABS;
   ABS.ReceiveData();
}

III.2.2.5. Pointers to class members

In C++, a pointer to a function cannot be assigned the address of a member function even if their type and parameter list are completely the same. The reason for that is that (non-static) member functions have their effects on the instances of their class. This is also true for the pointers to data members. (Another important difference between these pointers and traditional ones is that the later detailed polymorphism also has its effect if virtual member functions are called with pointers.)

In order to define pointers correctly, we have to use the name of the class and the scope operator:

class Class;

forward class declaration,

int Class::*p;

p is a pointer to a data member of type int,

void (Class::*pf unct )(int);

pfunct may point to a member function that is called with an argument of type int and that returns no value.

In the following example, we present how to use pointers to class members. We will access both class members by pointers. If such pointers are used, the members are referenced by the operators .* (dot asterisk) and ->* (arrow asterisk) instead of traditional operators. In order that the address of data members and member functions could be obtained, the "address of" operator (&) has to be used.

#include <iostream>
using namespace std;
 
class Class {
  public:
    int a;
    void f(int b) { a += b;}
};
 
int main() {
   // pointer to a data member of Class of type int
   int Class::*intptr = &Class::a;
 
   // pointer to the member function of Class 
   // of type void and with a parameter of type int
   void (Class::* functptr)(int) = &Class::f;
 
   // creating object instances
   Class object;
   Class * pobject = new Class();
 
   // accessing the data member with a pointer
   object.*intptr = 10;
   pobject->*intptr = 100;
 
   // calling the member function f() with a pointer
   (object.*functptr)(20);
   (pobject->*functptr)(200);
 
   cout << object.a << endl;    // 30
   cout << pobject->a << endl;    // 300
   delete pobject;
}

By using typedef, expressions containing pointers are easier to be handled:

typedef int Class::*pointer_int;
 
typedef void (Class::*pointer_funct)(int);
 
…
pointer_int intptr = &Class::a;
 
pointer_funct functptr = &Class::f;

III.2.3. Operator overloading

So far, operations belonging to a class have been realised in the form of member functions. And doing operations has meant calling member functions. Program codes become more legible if an operator of a similar content can be used instead of calling functions.

C++ offers the possibility to link a function created by a programmer to a traditional operator, extending the functioning of that operator. This function is called automatically if an operator is used in a specific context.

However, an operator function can be used if one of its parameters is a class of type class or struct. This means that functions without parameters and functions using only arguments of a basic data type cannot become operator functions. General declaration of operator functions:

        type operator op(parameterlist);

where the sequence op can be replaced by any of the following operators:

[]

()

.

->

++

--

&

new

*

+

-

~

!

/

%

new[]

<<

>>

<

>

<=

>=

==

delete

!=

^

|

&&

||

=

*=

delete[]

/=

%=

+=

-=

<<=

>>=

&=

 

^=

|=

,

->*

       

After type conversion, an operator function will have the following form:

        operator type();

The following operators cannot be overloaded : member selection (.), indirect member selection (.*), scope (::), conditional (?:) and the operators sizeof and typeid since their overloading would result in undesired side effects.

The assignment (=), the "address of" (&) and the comma (,) operations can be applied to objects without overloading.

It should be noted that overloading operators does not result in modifying the operator precedence and associativity, and it is not possible to introduce new operations.

III.2.3.1. Creating operator functions

The choice of an operator function, which realise operator overloading, depends largely on the chosen operator. The following table summarises the possibilities. The type and return value of most operator functions can be provided without restrictions.

Expression

Operator( )

Member function

External function

♣a

+ - * & ! ~

++ --

A::operator ()

operator (A)

a♣

++ --

A::operator (int)

operator (A, int)

a♣b

+ - * / % ^ &

| < > == != <=

>= << >> && || ,

A::operator (B)

operator (A, B)

a♣b

= += -= *= /=

%= ^= &= |= <<=

>>= []

A::operator (B)

-

a(b, c...)

()

A::operator()(B, C...)

-

a->b

->

A::operator->()

-

Operator functions are generally defined within classes in order that the features of user-defined types could be extended. The operators =, (), [] and -> can only be overloaded by non-static member functions. The operators new and delete are overloaded with static member functions. All other operator functions can be created as member functions or external (in general friend) functions.

The table above summarise well the things we have just said because it categorises into groups the overloadable operations of C++ while leaving out operators of dynamic memory management. The character ♣ in the table replaces the operator, whereas a, b and c are the objects of a class A, B and C.

Most of the overloadable C++ operators can be divided into two categories on the basis of the number of their operands. The following table summarises how to call these two types of operator functions.

Binary operands:

Realisation

Syntax

Actual call

member function

X op Y

X.operator op(Y)

external function

X op Y

operator op(X,Y)

Unary operands:

Realisation

Syntax

Actual call

member function

op X

X.operator op()

member function

X op

X.operator op(0)

external function

op X

operator op(X)

external function

X op

operator op(X,0)

When certain operations are redefined, there are other things that have to be considered than in general. These operators are treated more in the following parts of this chapter.

As an example, let's have a look at the class Vector in which the indexing ([]), value assignment (=) and addition (+, +=) operators are overloaded. The reason for modifying the value assignment operator is to be able to copy better the elements of an array. The operator + is realised by a friend function since the resulting vector does not belong to any of the operands logically. On the contrary, overloading the operation += is carried out by a member function since the elements of the left side operand are modified. The whole declaration of the class (together with inline functions) is contained by the file Vector.h.

#ifndef __VectorH__
#define __VectorH__
 
class Vector {
inline friend Vector  operator+ (const Vector& v1, 
                                 const Vector& v2);
  private:
    int size, *p;
  public:
    // ----- Constructors -----
    // initialising a vector of a given size
    Vector(int n=10) {
        p = new int[size=n];
        for (int i = 0; i < size; ++i)  
            p[i] = 0;    // initialising all elements to zero
    }
    // Initialising by another vector - a copy constructor
    Vector(const Vector& v) {
         p = new int[size=v.size];
         for (int i = 0; i < size; ++i) 
            p[i] = v.p[i];   // copying all elements
    }
    // Init.by a traditional vector with n number of elements
    Vector(const int a[], int n) {
        p = new int[size=n];
        for (int i = 0; i < size; ++i)
           p[i] = a[i];
    }
 
    // ----- Destructor -----
    ~Vector() {delete[] p; }
 
    // ----- Member function returning the size -----
    int GetSize() const { return size; } 
 
    // ----- Operator functions -----
    int& operator [] (int i) {
        // checking whether index is within bounds
        if (i < 0 || i > size-1)  
            throw i;
        return p[i];
    }
 
    const int& operator [] (int i) const {
        return p[i];
    }
 
    Vector operator = (const Vector& v) {
        delete[] p;
        p=new int [size=v.size];
        for (int i = 0; i < size; ++i)
           p[i] = v.p[i];
        return *this;
    }
 
    Vector operator += (const Vector& v) {
        int m = (size < v.size) ? size: v.size;
        for (int i = 0; i < m; ++i)
           p[i] += v.p[i];
        return *this;
    }
};
 
// ----- External function  -----
inline Vector operator+(const Vector& v1, const Vector& v2) {
    Vector sum(v1);
    sum+=v2;
    return sum;
}
#endif

In order that the example program code be more easily understood, we will give now the readers some further remarks.

  • Two operator functions were created for the operation of indexing, compilers use the second one with constant vectors. The two operator[]() functions are the overloaded versions of each other although their parameter list is the same. This is possible because C++ compilers also store whether the function is of type const or not in the trace of the function.

  • The pointer this points to an object; however, the expression *this designates the object itself. Functions of type Vector that return a *this value actually return the actual copy of the object. (It should be noted that functions of type Vektor& return a reference to the actual object because of the statement return *this;)

The usage of the class Vector is exemplified by the following code:

#include <iostream>
using namespace std;
#include "Vector.h"
 
void show(const Vector& v) {
  for (int i=0; i<v.GetSize(); i++)
    cout<<v[i]<<'\t';
  cout<<endl;
}
 
int main() {
    int a[5]={7, 12}, b[7]={2, 7, 12, 23, 29};
    Vector x(a,5);     // x:  7  12   0   0  0
    Vector y(b,7);     // y:  2   7  12  23 29  0  0
    Vector z;          // z:  0   0   0   0  0  0  0  0  0  0
    try {
       x = y;          // x:  2   7  12  23 29  0  0
       x = Vector(a,5);// x:  7  12   0   0  0
       x += y;         // x:  9  19  12  23 29
       z = x + y;      // z: 11  26  24  46 58
       z[0] = 102;
       show(z);        // z:102  26  24  46 58
    }
    catch (int n) {
        cout<<"Not a valid array index: "<<n<<endl;
    }
    const Vector v(z);
    show(v);            // v:102  26  24  46 58
    // v[0] = v[1]+5;   // error: assignment of read-only…
}

III.2.3.2. Using type conversion operator functions

C++ language supports that classes could be assigned type conversion. The declaration of a user-defined operator function doing type conversion:

        operator type();

The type of the return value of the function equals with the type figuring in the name of the function. The type conversion operator function can only be a function without return value and argument list.

In the following example, the type Complex is realised as a class. The only constructor in the example not having an argument of type Complex converts a double into a Complex. For the opposite conversion, we create a conversion operator named double.

#include <cmath>
#include <iostream>
using namespace std;
 
class Complex {
  public:
    Complex () { re=im=0; }
    Complex(double a) : re(a), im(0) { }
    // conversion constructor
    Complex(double a, double b) : re(a), im(b) { }
    // conversion operator
    operator double() {return sqrt(re*re+im*im);}
 
    Complex operator *= (const Complex & k) {
       Complex t;
       t.re=(k.re*re)-(k.im*im);
       t.im=(k.re*im)+(re*k.im);
       return *this = t;
    }
    void Print() const { cout << re << "+" << im << "i"; }
  private:
    double re, im;
  friend Complex operator*(const Complex&, const Complex&);
};
 
Complex operator*(const Complex& k1, const Complex& k2) {
   Complex k=k1;
   k*= k2;
   return k;
}
 
int main() {
   Complex k1(7), k2(3,4), k3(k2);
   cout << double(k3)<< endl;           // printed value:  5
   cout <<double(Complex(10))<< endl;   // printed value: 10
   Complex x(2,-1), y(3,4);
   x*=y;
   x.Print();                           // 10+5i
}

It should be noted that three constructors of the class Complex can be replaced by one if default arguments are used:

        Complex(double a=0, double b=0) : re(a), im(b) {}

III.2.3.3. Extending classes with input/output operations

C++ makes it possible for us to "teach" I/O data streams based on classes to handle the objects of user-defined classes. From among the data stream classes, istream is responsible for data input, whereas ostream for data output.

In order to carry out input/output operations, we use the overloaded versions of the operators >> and <<. In order to achieve the desired functioning, we should write our proper versions of the above mentioned operations as friend operator functions, as it can be seen in the extended version of the class Complex:

#include <cmath>
#include <sstream>
#include <iostream>
using namespace std;
 
class Complex {
  public:
    Complex(double a=0, double b=0) : re(a), im(b) {}
    operator double() {return sqrt(re*re+im*im);}
 
    Complex operator *= (const Complex & k) {
       Complex t;
       t.re=(k.re*re)-(k.im*im);
       t.im=(k.re*im)+(re*k.im);
       return *this = t;
    }
  private:
    double re, im;
  friend Complex operator*(const Complex&, const Complex&);
  friend istream & operator>>(istream &, Complex &);
  friend ostream & operator<<(ostream &, const Complex &);
};
 
Complex operator*(const Complex& k1, const Complex& k2) {
   Complex k=k1;
   k*= k2;
   return k;
}
 
// The format of data input: 12.23+7.29i and 12.23-7.29i
istream & operator>>(istream & is, Complex & c) {
   string s;
   getline(is, s);
   stringstream ss(s);
   if (!(ss>>c.re>>c.im))
      c=Complex(0);
   return is;
}
 
// The format of data output: 12.23+7.29i and 12.23-7.29i
ostream & operator<<(ostream & os, const Complex & c) {
   os<<c.re<<(c.im<0? '-' : '+')<<fabs(c.im)<<'i';
   return os;
}
 
int main() {
   Complex a, b;
   cout<<"Please enter a complex number: ";  cin >> a;
   cout<<"Please enter a complex number: ";  cin >> b;
   cout<<"The product of the complex numbers: " << a*b;
   cout<<endl;
}

III.3. Inheritance (derivation)

The previous sections treated how tasks can be solved with classes independent from one another. However, object-oriented programming offers more features besides that. When a problem is processed in an object-oriented manner, the program developing method named derivation (inheritance) is often applied. Derivation makes it possible to use the data and operations of already existing classes in a new aspect or to use or expand them in compliance with the need of the new task. Consequently, problems are not solved with the help of one (big) class but with the help of a system (generally a hierarchy) of classes.

Inheritance is the most important feature of object-oriented C++. This mechanism makes it possible to create (to derive) new classes from already existing ones. Derivation means that a new class inherits the public and protected properties (data members) and behaviour (member functions) of already existing classes and it then uses them as its own. However, already existing classes may be extended with a new class, new data members and member functions may be defined or inherited member functions may be reinterpreted (replaced) if they become deprecated concerning their functioning (polymorphism).

Specialised literature uses many expressions to designate the notions related to inheritance. First, we will recapitulate these expressions and underline those that are used in C++.

class A, that is derived or from which members are inherited:

base class , ancestor class , parent class, superclass

the operation:

inheritance , derivation , extending, subclassing

class B, the result of derivation:

descendant class , derived class , extended class, child class, subclass

The C++ program code that realises the relation above:

class ClassA {
    // ...
};
 
class ClassB : public  ClassA {
    // ...
};

Specialised literature does not use notions consequently: for example, the notions 'base class' or 'ancestor class' of a given class may designate any ancestor and the notions 'descendant class' or 'derived class' may be used to designate any class created by derivation. The present book uses these notions to designate direct ancestor and direct descendant classes, respectively.

The multiple inheritance of I/O classes in C++
Figure III.5. The multiple inheritance of I/O classes in C++


C++ supports multiple inheritance, during which a new class is derived from more base classes (that is it has more direct ancestors) (Figure III.5). The class structure that results from multiple inheritance has a mesh structure which is difficult to manage and to interpret. That is why, this solution is only used at a restricted extent. So it is often replaced with single inheritance. The latter means that a class may have at most one ancestor class and any number of descendants. Multi-level inheritance results in a real tree structure (class hierarchy) (Figure III.6).

Hierarchy of geometrical classes
Figure III.6. Hierarchy of geometrical classes


III.3.1. Derivation of classes

A derived (descendant) class is a class that inherits its data members and member functions from one or more already defined class(es). The class from which a derived class inherits is called base class (ancestor class). A derived class may also become a base class for other classes, so it can contribute to a class hierarchy.

A derived class inherits all the members of its base class; however, it only have access to the public and protected members of its base class as its own. The protected access involves a double behaviour. It means private access to the users of the given class, who create objects from that; however, it offers public access to developers who derive new classes from it. In general, member functions are defined to be public or protected while data members are protected or private. (Private access hides these members even from the member functions of their derived classes.) Derived classes may complete inherited members with new data members and member functions.

The place where a derivation is indicated in a program code is the class header where the mode of derivation (public, protected, private) is indicated before the names of base classes:

class Derived : public Base1, ...private BaseN
{
 // the class body
};

Independently of their access restriction in their base class, constructors, destructors, assignment operator and friend relations are not inherited. In most cases, it is public inheritance that is used because, in this way, descendant objects can replace their ancestor objects in all contexts.

The following example shows how to use inheritance by defining points in a two-dimensional plane and in a three-dimensional space. The resulting class hierarchy is very simple (the red arrow points directly to the direct base class):

class Point2D {
   protected:
      int x,y;
   public:
      Point2D(int a = 0, int b = 0) { x = a; y = b; }
      void GetPoint2D(int& a, int& b) const { a=x; b=y;}
      void Move(int a=0, int b=0) { x = a; y = b; }
      void Move(const Point2D& p) { x = p.x; y = p.y; }
      void PrintOut() const {
               cout<<'('<<x<<','<<y<<')'<<endl; } 
}
 
class Point3D : public Point2D {
   protected:
      int z;
   public:
      Point3D(int a=0, int b=0, int c=0):Point2D(a,b),z(c) {}
      Point3D(Point3D & p):Point2D(p.x, p.y),z(p.z) {}
      void GetPoint3D(int& a, int& b, int& c) const { 
           a = x; b = y; c = z; }
      void Move(int a=0, int b=0, int c=0) { 
           x = a; y = b; z = c; }
      void Move(const Point3D& p) { 
           Point2D::x = p.x; y = p.y; z = p.z;}
      void PrintOut() const {
           cout<<'('<<x<<','<<y<<','<<z<<')'<<endl;}
};
 
void Visualise(const Point2D & p) {
   p.PrintOut();
}
 
int main() {
  Point2D p1(12,23), p2(p1), p3;
  Point3D q1(7,29,80), q2(q1), q3;
  p1.PrintOut();            // (12,23)
  q1.PrintOut();            // (7,29,80)
  // q1 = p1;            // ↯ - error!
  q2.Move(10,2,4);
  p2 = q2;
  p2.PrintOut();            // (10,2)
  q2.PrintOut();            // (10,2,4)
  q2.Point2D::PrintOut();    // (10,2)
  Visualise(p2);              // (10,2)
  Visualise(q2);              // (10,2)
}

In the example, elements that are marked with blue are program elements used as a consequence of inheritance. These elements are treated later in this chapter.

It can be seen that the objects of the class created by public derivation replace the object of the base class in all cases (assignment, function argument, ...):

  p2 = q2;
  Visualise(q2);

The reason for that is that a derived class completely comprises its base class in case of inheritance. However, this is not true in the opposite direction, so the following value assignment leads to a compilation error:

  q1 = p1;    // ↯ 

The keywords public, protected and private used in a derivation list restrict the access of inherited (public and protected) members in their new classes, in the way summarised in the following table:

Mode of inheritance

Access in the base class

Access in the derived class

public

public

protected

public

protected

protected

public

protected

protected

protected

private

public

protected

private

private

When a class is derived as public, the inherited members keep their access mode in their base class, whereas if a derivation is private, the inherited members become the private members in the derived class, therefore they become hidden to the users of the new class and to developers who would develop it further. If a class is derived as protected, the inherited members become protected in the new class so they can be further inherited. (The default derivation mode for base classes of type class is private, whereas for those of type struct, it is public.)

This default behaviour leads to an appropriate result in most cases and offers an appropriate access to the inherited members of derived classes. However, the access of any member (the access type of which is protected or public in the base class) can be manually set directly, too. For that purpose, the name of the members qualified with the base class has to be simply inserted in the appropriate access type group. It should be made sure that a new access type cannot give members an access superior to that in their base class. For example, if a member that is protected in the ancestor class is inherited privately, it automatically becomes private in the derived class, but it can be placed in the protected access group (but not in the public group).

To exemplify this, let's derive by private derivation the class named Point3D but let's assign the data members the same access restriction as the one they had if the derivation was public.

class Point3D : private Point2D {
   protected:
      int z;
      Point2D::x;
      Point2D::y;
   public:
      Point3D(int a=0, int b=0, int c=0):Point2D(a,b),z(c) {}
      Point3D(Point3D & p):Point2D(p.x, p.y),z(p.z) {}
      void GetPoint3D(int& a, int& b, int& c) const {
           a = x; b = y; c = z; }
      void Move(int a=0, int b=0, int c=0) {
           x = a; y = b; z = c; }
      void Move(const Point3D& p) {
           x = p.x; y = p.y; z = p.z;}
      void PrintOut() const {
           cout<<'('<<x<<','<<y<<','<<z<<')'<<endl;}
      Point2D:: GetPoint2D;
};

III.3.2. Initialising base class(es)

In order that base class(es) be initialised, it is the extended version of member initialisation lists that is used, where, besides members, the constructor calls of direct ancestors are also enumerated.

class Point3D : public Point2D {
   protected:
      int z;
   public:
      Point3D(int a=0, int b=0, int c=0):Point2D(a,b),z(c) {}
      Point3D(Point3D & p):Point2D(p.x, p.y),z(p.z) {}
      // …
};

When a derived class is instantiated, the compiler calls the constructors in the following order:

  • The constructors of the base class are executed in the order corresponding to the initialisation list.

  • The constructors of the member objects of the derived class are called in the order in which object members were defined (they do not figure in this example).

  • The constructor of the derived class is executed.

Calling the constructor of the ancestor class is not necessary, if the base class has a constructor without parameters, which is called automatically by compilers. Since this condition is fulfilled in this example, the second constructor can be realised in the following form:

      Point3D(Point3D & p) { *this = p;}

(Another condition of that solution is public inheritance, which is also fulfilled.)

When a class hierarchy is developed, it is enough that each class takes care of initialising only its direct ancestor(s). In that way, all parts of the instance of classes that are situated at a higher level (farer from the root) are automatically assigned an initial value if the object is created.

When the derived object instance is destroying, destructors are executed in the order that is reverse to the above described one.

  • The destructor of the derived class is executed.

  • The destructors of the member objects of the derived class are called in the order reverse to their definition.

  • The destructors of base classes are executed, in an order opposite to that of the classes in the derivation list.

III.3.3. Accessing class members in case of inheritance

In Section III.2 class members were divided into two groups on the basis of their access: accessible and inaccessible. These two groups are further nuanced by inherited and not inherited categories. Section III.3.1 that detailed the way of deriving classes, described how basic access restrictions work. Now we will only give an overview of further solutions that precise what we have already learnt about inheritance.

III.3.3.1. Accessing inherited members

In general, the inherited members of derived classes can be accessed in the same way as their own members. However, if a data member or a member function is created with the same name in the derived class as the name of an inherited member, then the member of the ancestor class becomes hidden. In such cases, the scope operator has to be used to reference them:

        Class_name::member_name

Compilers identify member names together with their class scope, that is why all member names can be used in the form above. The example code of Section III.3.1 shows us some examples for the things that has been said.

class Point3D : public Point2D {
   protected:
      int z;
   public:
      Point3D(int a=0, int b=0, int c=0):Point2D(a,b),z(c) {}
      Point3D(Point3D & p):Point2D(p.x, p.y),z(p.z) {}
      // …
      void Move(const Point3D& p) { 
           Point2D::x = p.x; y = p.y; z = p.z;}
      // …
};
int main() {
  Point2D p1(12,23), p2(p1), p3;
  Point3D q1(7,29,80), q2(q1), q3;
  q2.Point2D::PrintOut();
  q2.Point2D::Move(1,2); // Moving in the x-y plane
  q2.Point2D::Move(p1);
  // …
}

The following table summarises what kind of members the classes of the example code have access. If a member is hidden, it is provided with their class name:

The members of the base class Point2D:

The members of the derived class Point3D

protected: x, y

public: Point2D(),

GetPoint2D(), Move(int…), Move(const…), PrintOut()

protected: x, y, z

public: Point3D(int…), Point3D(Point3D&…),

GetPoint2D(),Point2D()::Move(int…), Point2D()::Move(const…), Point2D()::PrintOut(), GetPoint3D(), Move(int…), Move(const…), PrintOut()

III.3.3.2. The friend relationship in inheritance

In a derived class, a friend of the base class can only access the members inherited from the base class. A "friend" of a derived class can only access public and protected members from the base class.

III.3.4. Virtual base classes in case of multiple inheritance

In case of multiple inheritance, it may be a problem if the same base class appears as many instances in the derived class. If virtual base classes are used, problems of that type can be avoided (Figure III.7).

class Base {
    int q;
  public:
     Base(int v=0) : q(v) {};
     int GetQ() { return q;}
     void SetQ(int q) { this->q = q;}
};
 
// the virtual base class named Base
class Base1 : virtual public Base {
    int x;
 public:
    Base1(int i): x(i) {}
};
 
// the virtual base class named Base
class Base2: public virtual Base {
    int y;
  public:
    Base2(int i): y(i) {}
};
 
class Descendant:  public Base1,  public Base2 {
    int a,b;
  public:
    Descendant(int i=0,int j=0): Base1(i+j),Base2(j*i),a(i),b(j) {}
};
int main() {
   Descendant descendant;
   descendant.Base1::SetQ(100);
   cout << descendant.GetQ()<<endl;          // 100
   cout << descendant.Base1::GetQ()<<endl;   // 100
   cout << descendant.Base2::GetQ()<<endl;   // 100
   descendant.Base1::SetQ(200);
   cout << descendant.GetQ()<<endl;          // 200
   cout << descendant.Base1::GetQ()<<endl;   // 200
   cout << descendant.Base2::GetQ()<<endl;   // 200
}

Using virtual base classes
Figure III.7. Using virtual base classes


A virtual base class will only be present in only one instance in its derived classes, independently of its number of occurrences in the inheritance chain. In the example, the data member named q of the virtual base class are inherited both by the base classes Base1 and Base2. Because of its virtuality, the class named Base occurs only in one instance, so Base1::q and Base2::q reference the same data member. Without using the keyword virtual, Base1::q and Base2::q designate different data members, which leads to a compilation error, since the compiler will not know for sure how to resolve the reference descendant.GetQ().

III.3.5. Inheritance and/or composition?

A big advantage of C++ programming language is that it supports the reusability of program code. Reusing means that a new program code is made without modifying the original one. If the object-oriented tools of C++ are used, there are three approaches to choose from:

  • The most simple and frequent reuse of a code stored in a given class is when an object instance is created or when already existing objects ( cin , cout , string , STL etc.) are used in a program.

class X {
   // …
};
      
int main() {
   X a, *pb;
   pb = new X();
   cout<<"C++"<<endl;
   // …
   delete pb;
}
  • Another possibility is to place objects of other classes in our own codes as member objects. Since the new class is created by reusing already existing ones, this method is called composition. During composition, the relation between new and included objects becomes a (has-a) relation. If the new object will only contain a pointer or a reference to other objects, it is called an aggregation.

class X {
   // …
};
 
class Y {
  X x;    // composition
};    
 
class Z {
  X& x;  // aggregation
  X *px;
};
  • The third solution is related to the subject of the present chapter. When a new class is created by public derivation from other classes, then the relationship is of an is-a type. This relation means that a derived object behaves exactly the same way as its ancestor class (that is the derived object is also an ancestor object); however, this is not true in the reverse direction.

// base class
class X {
   // …
};
 
// derived class
class Y : public X {
   // …
};    

If a problem is solved in an object-oriented way, it has to be determined that the desired model is achieved better by inheritance or by composition. The decision is not easy to take but the following guidelines may help a lot:

  • The starting point should be composition. However, if later it turns out that a class is actually a special type of another class, inheritance should be used.

  • Derivation is really needed if there is a need to cast the type of a derived class to the base class. For example, if all elements of a geometrical system is to be stored in a linked list.

  • The starting point should be composition. However, if later it turns out that a class is actually a special type of another class, inheritance should be used.

The following two example codes clearly show the differences and similarities between composition and derivation. In both cases, it is the class Point that is reused. This class is stored in the file named Point.h.

#ifndef __POINT_H__
#define __POINT_H__
class Point {
   protected:
      int x,y;
   public:
      Point(int a = 0, int b = 0) : x(a), y(b) {}
      Point(const Point& p) : x(p.x), y(p.y) {}
      int GetX() const { return x; }
      int GetY() const { return y; }
      void SetX(int a) { x = a; }
      void SetY(int a) { y = a; }
      void Move(int a, int b) { x = a; y = b; }
      void Move(const Point& p) { x = p.x; y = p.y; }
      void PrintOut() const { cout<<'('<<x<<','<<y<<')'<< endl; }
};
#endif

III.3.5.1. Reuse with composition

In real life, complex objects are often made up of smaller and simpler objects. For example, a car can be put together from a chassis, an engine, some tyres, a gear box, steerable wheels and a huge quantity of other spare parts. If the composition is done, the car has-a gearbox, has an engine, etc.

In the example named Circle, the circle has a centre which is stored in a member object of type Point named p. It should be noted that only the public members of the class Point can be accessed from the member functions of the class Circle.

#include "Point.h"
 
class Circle {
  protected:
    int r;
  public:
    Point p; // composition
    Circle(int x=0, int y=0, int r=0)
        : p(x, y), r(r) {}
    Circle(const Point& p, int r=0)
        : p(p), r(r) {}
    int GetR() {return r;}
    void SetR(int a) { r = a; }
};
 
int main() {
    Circle c1(100, 200, 10);
    c1.p.PrintOut();
    cout<<c1.p.GetX()<<endl;
    cout<<c1.GetR()<<endl;
}

III.3.5.2. Reuse by public inheritance

Like for composition, there are many examples in real life for inheritance. Everyone inherits genes from his/her parents; C++ language has also inherited many things from C language, which also inherited one part of its properties of its ancestors. In inheritance, the attributes and behaviour of base class(es) are obtained directly, and they may be extended or precised.

In our example, the object of type Point as the centre of the circle becomes integrated in the object of type Circle, which completely complies with the definition of a circle. Contrary to composition, the protected and public members of the class Point can be accessed from the member functions of the class Circle.

#include "Point.h"
 
class Circle : public Point { // inheritance
  protected:
    int r;
  public:
    Circle(int x=0, int y=0, int r=0)
        : Point(x, y), r(r) {}
    Circle(const Point & p, int r=0)
        : Point(p), r(r) {}
    int GetR() {return r;}
    void SetR(int a) { r = a; }
};
 
int main() {
    Circle c1(100, 200, 10);
    c1.PrintOut();
    cout<<c1.GetX()<<endl;
    cout<<c1.GetR()<<endl;
}

III.4. Polymorphism

In C++, polymorphism rather means that the object of a derived class is accessed by a pointer or a reference in the base class. Although the subject of this subsection is the solution called subtype or run-time polymorphism or simply overriding, let's review further "polymorphous" solutions of C++.

  • Coercion polymorphism means implicit and explicit type casts. In that case, the polymorphism of a given operation is made possible by different types that may be converted if needed.

  • As an opposite to coercion, the so-called ad-hoc („for that purpose”) polymorphism is better known by the name of function overloading. In that case, a compiler chooses the appropriate function from the variants on the basis of parameter types.

  • The extended version of this polymorphism is called parametrical or compile-time polymorphism, which makes it possible to execute the same code with any type. In C++, parametric polymorphism is realised by function and class templates. Using templates actually means reusing a C++ source code.

Previously, we have said that a derived class inherits all properties and behaviour (operations) of its ancestor. These inherited member functions can be reused without restrictions in the objects of the derived class since they comprise their ancestors. Since a derived class is often specialised, it may be necessary that certain inherited functions work in another way. This need is satisfied by introducing virtual member functions. Thanks to real-time polymorphism, an object may react to the same message in another way, depending on the level it occupies in the class hierarchy. And the choice of the member function that will be called from the inheritance chain only becomes known during execution (late binding).

III.4.1. Virtual member functions

A virtual function is a public or protected member function of the base class. It can be redefined in the derived class in order that the behaviour of the class would change. A virtual function is generally called by a reference or a pointer of a public base class, the actual value of which is determined at run-time (dynamic binding, late binding).

In order that a member function become virtual, the keyword virtual has to be provided in the class before the declaration of the function:

class Example {
  public:
    virtual int vf();
};

It is not necessary that a virtual function in the base class have a definition as well. Instead, the prototype of the function should be ended with the expression =0; . In that case, it is a so-called pure virtual function:

class Example {
  public:
    virtual int pvf() = 0;
};

An object instance cannot be created from a class containing one or more pure virtual functions (an abstract class). An abstract class can only be used as a starting point, as a base class for inheritance.

In case a member function becomes virtual at a level in the class hierarchy, then it can be replaced in any subsequent class in the inheritance chain.

III.4.2. Redefining virtual functions

If a function is declared as virtual in the base class, then it preserves this property during inheritance. In the derived class, a virtual function can be redefined with another version but the inherited version may also be used. If a new version is defined, it is not necessary to use the keyword virtual.

If a derived class inherits a pure virtual function, it has to be redefined by a new version, otherwise the new class will also be abstract. A derived class may contain virtual functions that it has not inherited from its direct base class.

The prototype of a virtual function redefined in the derived class has to be precisely identical (name, type, parameter list) with the one defined in the base class. If the parameterizations of the two declarations are not completely identical, then it is not redefining but overloading.

In the following example code, each shape calculates its own area and perimeter; however, visualisation is carried out by an abstract base class (Shape). The hierarchy of class is represented in the following figure:

// Abstract base class
class Shape {
  protected:
     int x, y;
  public:
     Shape(int x=0, int y=0) : x(x), y(y) {}
     virtual double Area()=0;
     virtual double Perimeter()=0;
     void Visualise() {
          cout<<'('<<x<<','<<y<<")\t";
          cout<<"\tArea: "<< Area();
          cout<<"\tPerimeter: "<< Perimeter() <<endl;
     }
};
 
class Square : public Shape {
  protected:
     double a;
  public:
     Square(int x=0, int y=0, double a=0) 
             : Shape(x,y), a(a) {}
     double Area() {return a*a;}
     double Perimeter() {return 4*a;}
};
 
class Rectangle : public Square {
  protected:
     double b;
  public:
     Rectangle(int x=0, int y=0, double a=0,  double b=0)
             : Square(x,y,a), b(b) {}
     double Area() {return a*b;}
     double Perimeter() {return 2*(a+b);}
};
 
class Circle : public Square {
  const double pi;
  public:
     Circle(int x=0, int y=0, double r=0)
             : Square(x,y,r), pi(3.14159265) {}
     double Area() {return a*a*pi;}
     double Perimeter() {return 2*a*pi;}
};
 
int main() {
     Square s(12,23,10);
     cout<<"Square: ";
     s.Visualise();
 
     Circle c(23,12,10);
     cout<<"Circle: ";
     c.Visualise();
 
     Rectangle r(12,7,10,20);
     cout<<"Rectangle: ";
     r.Visualise();
 
     Shape* shapes[3] = {&s, &c, &r} ;
     for (int i=0; i<3; i++)
       shapes[i]->Visualise();
}

Virtual functions and public inheritance make it possible to create external functions that can be called by every object in the class hierarchy:

void VisualiseAll(Shape& a) {
     cout<<"Area: "<<a.Area()<<endl;
     cout<<"Perimeter: "<<a.Perimeter()<<endl;
}

III.4.3. Early and late binding

In order to explain more how run-time polymorphism works, we will examine, through examples, the resolution of member function calls at compilation time (early binding) and at run-time (late binding).

In the example codes, two member functions having the same prototype are defined (GetName(), GetValue()) both in the base class and the derived class. The main() function contains a pointer and a reference to an instance of the derived class. The pointer (pA) and the reference (rA) are of the type of the base class.

III.4.3.1. Static early binding

During early binding, compilers integrate statically direct member function calls into the code. In the case of classes, this is the default operation mode, which is clearly reflected by the following example code.

Early binding example
Figure III.8. Early binding example


class Base {
 protected:
     int value;
 public:
     Base(int a=0) : value(a) { }
     const char* GetName() const { return "Base"; }
     int GetValue() const { return value; }
 };
 
class Derived: public Base {
 protected:
     int value;
 public:
     Derived(int a=0, int b=0) : Base(a), value(b) { }
     const char* GetName() const { return "Derived"; }
     int GetValue() const { return value; }
 };
 
int main() {
  Base a;
  Derived b(12, 23);
 
  a = b;
  Base &rA = b;
  Base *pA = &b;
 
  cout<<"a \t" <<  a.GetName()<<"\t"<<  a.GetValue()<<endl;
  cout<<"a \t" <<  b.GetName()<<"\t"<<  b.GetValue()<<endl;
  cout<<"rA\t" << rA.GetName()<<"\t"<< rA.GetValue()<<endl;
  cout<<"pA\t" << pA->GetName()<<"\t"<< pA->GetValue()<<endl;
}

The calls of the member function GetValue() is presented in Figure III.8. The results:

a       Base    12
b       Derived 23
rA      base    12
pA      Base    12

III.4.3.2. Dynamic late binding

The situation changes a lot (Figure III.8), if the member functions GetName(), GetValue() are made virtual in the class Base.

class Base {
 protected:
     int value;
 public:
     Base(int a=0) : value(a) { }
     virtual const char* GetName() const { return "Base"; }
     virtual int GetValue() const { return value; }
 };

The results have also changed:

a       Base      12
b       Derived   23
rA      Derived   23
pA      Derived   23

Late binding example
Figure III.9. Late binding example


Compilers place the calls of virtual functions indirectly in the code: as jumps to the address stored in the memory. The virtual method table (VMT) used for storing addresses is created for each class during execution, at the first instantiation of the given class. VMT contains the address of redefined virtual functions. Virtual functions having the same name and being situated in the class hierarchy have the same index in these tables, which makes it possible to replace completely virtual member functions.

III.4.3.3. Virtual method table

In case a class has one or more virtual member functions, compilers complete the object with a "virtual pointer" to the global data table called virtual method table (VMT) or virtual function table (VFTable). VMT contains function pointers to the virtual member functions redefined the last of the given class and the base classes (Figure III.10). The address of the virtual functions having the same name has the same index in these tables.

Virtual method tables of the example code
Figure III.10. Virtual method tables of the example code


VMTs for classes are created at run-time when the first constructor is called. As a consequence, the relation between the caller and the called member function is also realised at run-time. The compiler will only insert in the code a call that takes place by using the ith element of the VMT (call VMT[i]).

III.4.4. Virtual destructors

A destructor can be defined as a virtual function. If the destructor of the base class is virtual, then the destructor of all classes derived from that will also be virtual. That is why, it is sure that it is always the appropriate destructor that is called if the object ceases to exist even if a pointer or a reference of the type of a base class references that instance of the derived class.

In order to activate this mechanism, it is enough to place a virtual, empty destructor in a class somewhere in the beginning of the inheritance chain:

class Base {
 protected:
     int value;
 public:
     Base(int a=0) : value(a) { }
     virtual const char* GetName() const { return "Base"; }
     virtual int GetValue() const { return value; }
     virtual ~Base() {}
 };

III.4.5. Abstract classes and interfaces

As we have already seen, abstract classes are good bases for inheritance chains. In C++, there are no keywords to designate abstract classes, they can only be recognised if we know whether they contain pure virtual functions or not. The reason for treating them again in this part is to follow the traditions of other programming languages, and to find elements from them that can be realised in C++ as well.

The Java, C# and Object Pascal programming languages support only single inheritance; however, they make it possible to implement any number of interfaces. In a C++ environment, an interface is an abstract class that only has virtual functions. The only aim of interfaces is to force developers (if they derive them) to create the member functions that have been declared as public in them.

The traps of multiple inheritance can be avoided if the base classes only contain one "real" class (that is a class that has data members, too) and the others are only interface classes. (The names of interfaces classes generally begin with a capital "I".)

To exemplify interfaces, in a previous class named Point, we separate now the class storing the geometrical values and the interface defining the ability to move, since the latter is not always needed.

// the geometrical Point class
class Point {
   protected:
      int x, y;
   public:
      Point(int a = 0, int b = 0) : x(a), y(b) {}
      Point(const Point& p) : x(p.x), y(p.y) {}
      int GetX() const { return x; }
      int GetY() const { return y; }
      void SetX(int a) { x = a; }
      void SetY(int a) { y = a; }
      void PrintOut() const { cout<<'('<<x<<','<<y<<')'<< endl; }
};
 
// an abstract class needed for moving - interface
class IMove {
   public:
      virtual void Move(int a, int b) = 0;
      virtual void Move(const Point& p) = 0;
};
 
// a Point that is able to move
class MovingPoint: public Point, public IMove {
    public:
      MovingPoint(int a=0, int b=0) : Point(a,b) {}
      void Move(int a, int b) { x = a; y = b; }
      void Move(const Point& p) {
          x = p.GetX();
          y = p.GetY();
      }
};
 
int main() {
    Point fixPoint(12, 23);
    fixPoint.PrintOut();         // (12, 23)
 
    MovingPoint movingPoint;
    movingPoint.PrintOut();       // (0, 0)
    movingPoint.Move(fixPoint);
    movingPoint.PrintOut();       // (12, 23)
}

III.4.6. Run-time type informations in case of classes

Visual development kits store Run-time Type Information, RTTI for each object instances. With the help of them, it is executing environments that may be entrusted with identifying the type of objects, so new data members do not have to introduced for that purpose.

In order that RTTI mechanism function correctly, a polymorphous base class has to be created, that is at least a virtual member function should be inserted into it and the storage of RTTI has to be enabled. (The possibility to enable this feature can be found among the settings of the compiler.) In order that the type of pointers and references be identified, the operations dynamic_cast and typeid are used. In order that appropriate type cast be carried out, the operator dynamic_cast is used.

The operator typeid returns an object of type const type_info , the members of which provide information about the type of the operand. The character sequence returned by the member function named name() of the object depends on the used compiler. In order to use the operator, the header file typeinfo should be included in the code.

#include <typeinfo>
#include <iostream>
using namespace std;
 
class Ancestor {
   public:
      virtual void Vf(){} // without this, RTTI is not stored
      void FunctAncestor() {cout<<"Ancestor"<<endl;}
  };
 
class Descendant : public Ancestor {
   public:
      void FunctDescendant() {cout<<"Descendant"<<endl;}
 };
 
int main() {
   Descendant * pDescendant = new Descendant;
   Ancestor * pAncestor = pDescendant;
 
    // the type_info of the pointer:
   const type_info& tiAncestor = typeid(pAncestor); 
   cout<< tiAncestor.name() <<endl;
 
   // the type_info of the Descendant:
   const type_info& tiDescendant = typeid(*pAncestor); 
   cout<< tiDescendant.name() <<endl;
 
   // points to the Descendant?
   if (typeid(*pAncestor) == typeid(Descendant)) 
      dynamic_cast<Descendant *>(pAncestor)
                                      ->FunctDescendant();
 
   // points to the Descendant?
   if (dynamic_cast<Descendant*>(pAncestor)) 
      dynamic_cast<Descendant*>(pAncestor) 
                                      ->FunctDescendant();
   delete pDescendant;
} 

The following example code needs run-time type information if members of different classes are to be accessed.

#include <iostream>
#include <string>
#include <typeinfo>
using namespace std;
 
class Animal {
  protected:
     int legs;
  public:
     virtual const string Species() = 0;
     Animal(int n) {legs=n;}
     void Info() {
       cout<<"A(n) "<<Species()<<" has "
           <<legs<<" leg(s)."<<endl;
      }
};
 
class Fish : public Animal {
  protected:
     const string Species() {return "fish";}
  public:
     Fish(int n=0) : Animal(n) {}
     void Swims() {cout<<"swims"<<endl;}
};
 
class Bird : public Animal {
  protected:
     const string Species() {return "bird";}
  public:
     Bird(int n=0) : Animal(n) {}
     void Flies() {cout<<"flies"<<endl;}
};
 
class Mammal : public Animal {
  protected:
     const string Species() {return "mammal";}
  public:
     Mammal(int n=4) : Animal(n) {}
     void Runs() {cout<<"runs"<<endl;}
};
 
int main() {
    const int db=3;
    Animal* p[db] = {new Bird, new Fish, new Mammal};
 
    // accessing data without RTTI
    for (int i=0; i<db; i++)
      p[i]->Info();
 
    // processing with the help of RTTI
    for (int i=0; i<db; i++)
       if (dynamic_cast<Fish*>(p[i]))        // Fish?
             dynamic_cast<Fish*>(p[i])->Swims();
       else
       if (typeid(*p[i])==typeid(Bird))           // Bird?
             dynamic_cast<Bird*>(p[i])->Flies();
       else
       if (typeid(*p[i])==typeid(Mammal))     // Mammal?
             dynamic_cast<Mammal*>(p[i])->Runs();
 
    for (int i=0; i<db; i++)
        delete p[i];
}

For the sake of comparison, let's also have a look at the version of the code above that does not use run-time type information. In that case, the value of the virtual function Species() of the class Animal can be used to identify the class and type cast is carried out by the operator static_cast. It is only the content of the main() function that has changed:

int main() {
    const int db=3;
    Animal* p[db] = {new Bird, new Fish, new Mammal};
 
    for (int i=0; i<db; i++)
      p[i]->Info();
 
    for (int i=0; i<db; i++)
       if (p[i]->Species()=="fish")
             static_cast<Fish*>(p[i])->Swims();
       else
       if (p[i]->Species()=="bird")
             static_cast<Bird*>(p[i])->Flies();
       else
       if (p[i]->Species()=="mammal")
             static_cast<Mammal*>(p[i])->Runs();
 
    for (int i=0; i<db; i++)
        delete p[i];
}

The results of executing both versions are the same:

A(n) bird has two leg(s).
A(n) fish has 0 leg(s).
A(n) mammal has 4 leg(s).
flies
swims
runs

III.5. Class templates

The solutions of most typed languages depend on types: when a useful function or class is created, it only works correctly with data of the types fixed in it. If the same function/class is to be used for other types of data, it has to be rewritten by replacing the concerned types.

C++ language introduces function and class templates to prevent developers from doing this kind of type replacing method. The only task of a programmer in C++ is to write the needed function or class and to mark all types to be replaced, and the other things are done by compilers.

III.5.1. A step-be-step tutorial for creating and using class templates

As an introduction, we will review how to make and use templates step by step. For that purpose, we rely on knowledge already treated in the present book.

As an example, let's see the class named IntArray, which is a simplified class handling an one-dimensional integer array of 32 elements with bound index checking.

#include <iostream>
#include <cassert>
#include <cstring>
using namespace std;
 
class IntArray {
  public:
     IntArray(bool initialise = true) : size(32) {
        if (initialise) memset(storage, 0, 32*sizeof(int));
     }
     int& operator [](int index);
     const int size;
  private:
     int storage[32];
};
 
int & IntArray::operator [](int index) {
  if (index<0 || index>=32) assert(0);     // index error
  return storage[index];              // success
}

The objects of this class not only store 32 integer elements but also check index bounds when an element is accessed. For the sake of simplicity, a wrong index makes the program exit. When an array (an object) is created, all elements are initialised to zero,

IntArray a;

except for the case where the constructor is called by a false argument.

IntArray a(false);

The array stores the number of elements in the constant data member named size and redefines the indexing operator. The elements of the array can be accessed in the following way:

int main() {
    IntArray a;
    a[ 7] = 12;
    a[29] = 23;
    for (int i=0; i<a.size; i++)
        cout<<a[i]<<'\t';
}

What should we do if we do not want to store 32 elements or if we need to store data of type double? We should use a class template, which generalises the int types of the class IntArray (all marked with red in the example). And the number of elements (32) is passed to the class as a parameter.

#include <iostream>
#include <cassert>
#include <cstring>
using namespace std;
 
template <class type, int numberOfElements>
class Array {
  public:
     Array(bool initialise=true): size(numberOfElements) { 
        if (initialise) 
         memset(storage,0,numberOfElements*sizeof(type)); }
     type& operator [](int index);  
     const int size;
  private:
     type storage[numberOfElements];
};

In the case of external member functions, the name of the class has to be used together with the generic type and the parameter Array<type, numberOfElements>:

template <class type, int numberOfElements>
type & Array<type, numberOfElements>::operator [](int index) { 
  if (index<0 || index>=numberOfElements) 
     assert(0);                // index error
  return storage[index];    // success
}

It should be noted that the non-implicit inline member functions of a class template have to be included in each source file from which they are called. Otherwise, a compiler will not be able to create the source code of the function. In the case of a project consisting of more source modules, the elements of a class template (together with the member functions defined outside the class) should be placed in a header file, which can be included in all source modules without receiving a "multiply defined symbols" error message.

The main aim of a class template (a generic class) is to make compilers create the real, type-dependant class together with all of its components on the basis of the template, just as in the case of the already presented function templates. A class template is always used with parameters when objects are to be created:

Array<int, 32> av, bv(false);

By defining a type

typedef Array<int, 32> IntArray;

creating objects is simpler:

IntArray av, bv(false);

The size figuring in the definition of the template is a constant parameter, the value of which is made use of by the compiler during compilation. When a template is processed, the parameter is replaced by a constant value or a C++ constant (const). Of course, templates can also be created without constant parameters, which we will also do in the further sections of the chapter.

Before going further, let's see how a class template can help us. Its simplest usage form has already been presented, so we repeat now that code:

int main() {
    Array<int, 32> a;
    a[ 7] = 12;
    a[29] = 23;
    for (int i=0; i<a.size; i++)
        cout<<a[i]<<'\t';
}

The template created from that can be used also for storing character sequences and objects.

const int asize=8;
Array<char *, asize> s1;
s1[2] = (char*)"C++";
s1[4] = (char*)"java";
s1[7] = (char*)"C#";
for (int i=0; i<s1.size; i++)
    if (s1[i]) cout<<s1[i]<<'\t';

It should be noted that the solution that we use now to initialise all elements to zero is too radical for class type array elements, so it has to be avoided when the constructor of the class Array is called with a false argument. (In that case, the constructors of array elements are called, and then the constructor of the class Array<string, 8>, which frees by default the memory space of already initialised element objects.)

const int asize=8;
Array<string, asize> s2 (false);
s2[2] = "C++";
s2[4] = "java";
s2[7] = "C#";
for (int i=0; i<s2.size; i++)
    cout<<s2[i]<<'\t';

Of course, an array object can be created dynamically as well; however, then we should make sure to use indexing correctly and to access correctly the data member size. The array object can be accessed by the expression (*dt) or dt[0], which is followed by the indexing operator:

Array<double, 3> *dt;
dt = new Array<double, 3>;
(*dt)[0] =12.23;
 dt[0][1]=34.45;
 for (int i=0; i<dt->size; i++) 
      cout<<(*dt)[i]<<'\t';  
 delete dt; 

By dynamic memory allocation, the vector of five elements of the array objects of type Array<double, 3> can be easily created. In order to access data elements of type double, let's choose double indexing. The first index indicates the element within the dynamic array, whereas the second one within the object of type Array<double, 3>. Apparently, this solution leads to a two-dimensional array of type double.

Array<double, 3> *dm;
dm = new Array<double, 3> [5];
dm[0][1] =12.23;
dm[4][2]=34.45;
for (int i=0; i<5; i++) {
  for (int j=0; j<dm[0].size; j++)
      cout<<dm[i][j]<<'\t';
  cout<<endl;
}
delete []dm;

In case a static array is created instead of the dynamic memory allocation vector of five elements, the solution remains similar:

Array<int, 3> m[5]; 
m[0][1] = 12;
m[4][2] = 23;
for (int i=0; i<5; i++) {
  for (int j=0; j<m[0].size; j++)
      cout<<m[i][j]<<'\t';
  cout<<endl;
} 

Finally, let's have a look at the following instantiation where an object vector of five elements is created. All of its elements is of type Array<int,3>. The result is a kind of a two-dimensional array containing elements of type int.

Array< Array<int,3>, 5> p(false);
p[0][1] = 12;
p[4][2] = 23;
for (int i=0; i<p.size; i++) {
  for (int j=0; j<p[0].size; j++)
      cout<<p[i][j]<<'\t';
  cout<<endl;
} 

On the basis of the examples, it can be clearly seen that class templates offer an efficient programming tool. However, using them involves thinking in the way programmers do and involves having to know completely the functioning and features of object-oriented tools of C++ language. This is especially true if a hierarchy of class templates is to be realised. In the following parts, we will review the notions and techniques of creating and using templates.

Unfortunately, this is the most difficult part of programming in C++ because it requires the complete knowledge of this language. If the Reader does not intend to develop his/her own class templates, it is enough to go to Chapter III.5.6. which details how to use the Standard Template Library.

III.5.2. Defining a generic class

A parametrized or generic class makes it possible for us to use the parametrized class as a template to create new classes. So a given class definition can be used for all types.

Let's see how to define class templates in a general way, where type1,..typeN designate type parameters. In the template heading (template<>) type parameters are indicated by the keywords class or typename:

template <class type1, … class typeN> 
class Classname {    
   …
};

or

template <typename type1, … typename typeN> 
class Classname {
   …
};

The non-inline member functions of the class should be defined as follows:

template <class type1, … class typeN > 
functtype Classname< type1, … typeN> ::
                                Functname(parameter_list) {
   …
}

or

template <typename type1, … typename typeN > 
functtype Classname< type1, … typeN> :: 
                                Functname(parameter_list) {
   …
}

As an example, let's have a look at the generic class made from the class Point, that has implicit inline member functions.

template <typename type>
class Point {
   protected:
      type x, y;
   public:
      Point(type a = 0, type b = 0) : x(a), y(b) {}
      Point(const Point& p) : x(p.x), y(p.y) {}
      type GetX() const { return x; }
      type GetY() const { return y; }
      void SetX(type a) { x = a; }
      void SetY(type a) { y = a; }
      void PrintOut() const { cout<<'('<<x<<','<<y<<')'<< endl; }
};

The same Point class becomes much more complicated if one part of its member functions is defined outside the class:

template <typename type>
class Point {
   protected:
      type x, y;
   public:
      Point(type a = 0, type b = 0) : x(a), y(b) {}
      Point(const Point& p) : x(p.x), y(p.y) {}
      type GetX() const;
      type GetY() const { return y; }
      void SetX(type a);
      void SetY(type a) { y = a; }
      void PrintOut() const;
};
template <typename type>
type Point<type>::GetX() const { return x; }
 
template <typename type>
void Point<type>::SetX(type a) { x = a; }
 
template <typename type>
void Point<type>::PrintOut() const { 
     cout<<'('<<x<<','<<y<<')'<< endl; 
}

Both version of the class Point is a generic class or a class template, which is only a declaration available in the source code and in which the compiler only checks syntax. Compilation to a machine code only takes place where a template is instantiated with concrete type arguments, that is when template classes are created.

III.5.3. Instantiation and specialisation

The relation between a class template and the classes instantiated from it is similar to the relation between a normal class and its objects. A normal class determines how objects can be instantiated from it, whereas a template class contains information about how to generate instances from it.

A template can be defined in many ways. In implicit instantiation, type parameters are replaced by concrete types. First the version of the given type of a class is created (if it has not yet been created), then the object instance:

Point<double> p1(1.2, 2.3), p2(p1);
Point<int> *pp;  // the class Point<int> is not created

In explicit instantiation, the compiler is asked to create an instance of the class by using the given types, so when the object is being created, the class is ready to be used:

template class Point<double>;
…
Point<double> p1(1.2, 2.3), p2(p1);

There are cases when it is easier to use templates if the generic version is specialised for a purpose (explicit specialisation). Among the following declarations, the first one is a general template, the second one is a version tailored to pointers, the third is a version specialised to void* pointers.

template <class type> class Point {
  // the class template above
};
 
template <class type> class Point <type *> {
  // has to be created
};
 
template <> class Point <void *> {
  // has to be created
};

These specialised versions can be used in the case of the following instantiations:

Point<double> pa;
Point<int *> pp;
Point<void *> pv;

Let's examine the functioning of instantiation and specialisation in the case of templates with two parameters. In that case, leaving out one of the template parameters leads to a partial specialisation:

template <typename T1, typename T2>
class DataStream {
   public:
      DataStream() { cout << "DataStream<T1,T2>"<<endl;}
      // …
};
 
template < typename T1, typename T2>     // Specialisation
class DataStream<T1*, T2*> {
   public:
      DataStream() { cout << "DataStream<T1*,T2*>"<<endl;}
      // …
};
 
template < typename T1>             // Partial specialisation
class DataStream<T1, int> {
   public:
      DataStream() { cout << "DataStream<T1, int>"<<endl;}
      // …
};
 
template <>                    // Complete specialisation
class DataStream<char, int> {
   public:
      DataStream() { cout << "DataStream<char, int>"<<endl;}
      // …
} ;
 
int main() {
   DataStream<char, int> s4 ;       // Complete specialisation
   DataStream<int, double> s1 ;
   DataStream<double*, int*> s2;    // Specialisation
   DataStream<double, int> s3 ;    // Partial specialisation
}

III.5.4. Value parameters and default template parameters

In the introductory example of this chapter, the class template not only had a type parameter but also an integer type value parameter. With the help of that, a constant value was passed to the compiler during instantiation.

C++ supports default template parameters. Now let's assign default values to the parameters of the class template Array.

#include <iostream>
#include <cassert>
#include <cstring>
using namespace std;
 
template <typename type=int, int numberOfElements=32>
class Array {
  public:
    Array(bool initialise=true): size(numberOfElements) {
       if (initialise) 
         memset(storage, 0, numberOfElements*sizeof(type)); 
    }
    type& operator [](int index) {
       if (index<0 || index>=numberOfElements) assert(0); 
       return storage[index]; 
     }
     const int size;
  private:
     type storage[numberOfElements];
};

In that case, in order to create the type IntArray, the generic class can be specialised without arguments:

        typedef Array<> IntArray;

The following simple example shows how to realise the Stack data structure as a class template. The parameters of the stack template will also have default values.

#include <iostream>
#include <string>
using namespace std;
 
template<typename Type=int, int MaxSize=100>
class Stack {
   Type array[MaxSize];
   int sp;
  public:
   Stack(void) { sp = 0; };
   void Push(Type data) {
      if (sp < MaxSize) array[sp++] = data;
   }
   Type Pop(void) {
      return array[sp > 0 ? --sp : sp];
   }
   bool isEmpty(void) const { return sp == 0; };
};
 
int main(void) {
  Stack<double,1000> dStack; // stack of 1000 double elements
  Stack<string> sStack;      // stack of 100 string elements
  Stack<> iStack;            // stack of 100 int elements
 
  int a=102, b=729;
  iStack.Push(a);
  iStack.Push(b);
  a=iStack.Pop();
  b=iStack.Pop();
 
  sStack.Push("language");
  sStack.Push("C++");
  do {
     cout << sStack.Pop()<<endl;;
  } while (!sStack.isEmpty());
}

III.5.5. The "friends" and static data members of a class template

A class template may also have friends, which may behave differently. Those that do not have a template specification become common "friends" of all specialised classes. Otherwise, an external function can only be used as a friend function of the given instantiated class variant.

In the first example, each instantiation of the class template BClass becomes a friend class of the class ClassA:

#include <iostream>
using namespace std;
 
class ClassA {
   void Operation() { cout << "Operation carried out."<< endl; };
   template<typename T> friend class BClass;
};
 
template<class T> class BClass {
   public:
      void Execute(ClassA& a) { a.Operation(); }
};
 
int main() {
   BClass<int> b;
   BClass<double> c;
   ClassA a;
   b.Execute(a);
   c.Execute(a);
}

In the second example, the defined friend function (Funct) is a template itself:

#include <iostream>
using namespace std;
 
// Forward declarations
template <typename T> class Class;
template <typename T> void Funct(Class<T>&);
 
template <typename T> class Class {
    friend void Funct<T>(Class<T>&);
   public:
    T GetData(){return data;}
    void SetData(T a){data=a;}
   private:
    T data;
};
 
template<typename T> void Funct(Class<T>& x) {
    cout<<"Result: "<<x.GetData()<<endl;
}
 
int main() {
    Class<int> obj1;
    obj1.SetData(7);
    Funct(obj1);
    Class<double> obj2;
    obj2.SetData(7.29);
    Funct(obj2);
}

The static data members defined in a generic class have to be created for each template class:

#include <iostream>
using namespace std;
 
template<typename type> class Class {
  public:
    static int ID;
    static type data;
    Class() {}
};
 
// Definitions of static data members
template <typename type> int Class<type>::ID = 23;
template <typename type> type Class<type>::data = 12.34;
 
int main() {
    Class <double> dObj1, dObj2;
 
    cout << dObj1.ID++   << endl;           // 23
    cout << dObj1.data-- << endl;          // 12.34
 
    cout << dObj2.ID   << endl;             // 24
    cout << dObj2.data << endl;            // 11.34
 
    cout <<Class<double>::ID << endl;        // 24
    cout <<Class<double>::data << endl;      // 11.34
}

III.5.6. The Standard Template Library (STL) of C++

The Standard Template Library (STL) is a software library and is an integral part of the Standard Library of C++ language. STL is a collection of containers, algorithms and iterators, and also contains many fundamental computational algorithms and data structures. The elements of STL are template classes and functions. In order to use these, it is recommended to understand the basics of using templates in C++.

The following review cannot replace a deeper description of the whole STL; however, it is enough to be able to use the fundamental elements of the Library in program codes.

III.5.6.1. The structure of STL

The elements of the Library can be grouped into five groups:

  • containers – data structures making it possible to store data in memory (vector, list, map, set, deque, …)

  • adaptors – higher-level data structures based on containers (stack, queue, priority_queue)

  • algorithms - operations that can be carried out on data stored in containers (sort, copy, search, min, max, …)

  • iterators – generic pointers that ensure access to the data stored in containers (iterator, const_iterator, ostream_iterator<>, … )

  • function objects – functions are covered by classes, for other components (divides, greater_equal, logical_and, …).

In compliance with template management, these features can be integrated into source codes by including the corresponding header files. The following table recapitulates the most frequently used declaration files of STL:

Short description

Header file

Managing, sorting data in  containers and searching in them

<algorithm>

Associative container for storing bits: bitset

<bitset>

Associative containers that store elements: multiset (may have the same elements more times), and set (stores only unique elements)

<set>

Associative container storing key/value pairs in a 1:1 relation (map), or in a 1:n relation (multiset)

<map>

Predefined iterators, datastream iterators

<iterator>

Container: dynamic array

<vector>

Container: double ended queue

<deque>

Container: linear list

<list>

Container adaptor: queue

<queue>

Container adaptor: stack

<stack>

III.5.6.2. STL and C++ arrays

The algorithms and data stream iterators of STL can all be used for one-dimensional arrays in C++. This makes it possible to use the same operations for pointers and for iterators in C++: dereference (*), increment (++) etc. Most function templates expect a generic pointer to the beginning of the container (begin) and a pointer to the position after the last data (end) as arguments.

The following example carries out different operations with the help of STL algorithms on the elements of an array containing seven integers. Most of the algorithms (the number of which is more than 60) can be used successfully in traditional C++ program codes.

#include <iostream>
#include <iterator>
#include <algorithm>
using namespace std;
 
void PrintOut(const int x[], int n) {
    static ostream_iterator<int> out(cout,"\t");
    cout<< "\t";
    copy(x, x+n, out);
    cout<<endl;
}
 
void IntPrintOut(int a) {
    cout << "\t" << a << endl;
}
 
int main() {
    const int db = 7;
    int data[db]={2, 7, 10, 12, 23, 29, 80};
 
    cout << "Original array: " << endl;
    PrintOut(data, db);
 
    cout << "Next permutation: " << endl;
    next_permutation(data,data+db);
    PrintOut(data, db);
 
    cout << "In the reverse order: " << endl;
    reverse(data,data+db);
    PrintOut(data, db);
 
    cout << "Random shuffle: " << endl;
    for (int i=0; i<db; i++) {
       random_shuffle(data,data+db);
       PrintOut(data, db);
    }
 
    cout << "The greatest element: ";
    cout << *max_element(data,data+db) << endl;
 
    cout << "Finding an element:";
    int *p=find(data,data+db, 7);
    if (p != data+db)
      cout << "\tfound" <<endl;
     else
      cout << "\tnot found" <<endl;
 
    cout << "Sort: " << endl;
    sort(data,data+db);
    PrintOut(data, db);
 
    cout << "Printing out each element in a new line:"
                                                   << endl;
    for_each(data, data+db, IntPrintOut);
    PrintOut(data, db);
 
    cout << "Swap: " << endl;
    swap(data[2],data[4]);
    PrintOut(data, db);
 
    cout << "Filling the container: " << endl;
    fill(data,data+db, 123);
    PrintOut(data, db);
}

The results of the execution of the program:

Original array:
        2       7       10      12      23      29      80
Next permutation:
        2       7       10      12      23      80      29
In the reverse order:
        29      80      23      12      10      7       2
Random shuffle:
        10      80      2       23      29      7       12
        2       10      23      80      29      12      7
        7       12      2       10      80      29      23
        2       12      29      10      80      7       23
        12      23      7       29      10      2       80
        7       23      12      2       80      10      29
        7       12      23      2       29      10      80
The greatest element: 80
Finding an element:  found
Sort:
        2       7       10      12      23      29      80
Printing out each element in a new line:
        2
        7
        10
        12
        23
        29
        80
        2       7       10      12      23      29      80
Swap:
        2       7       23      12      10      29      80
Filling the container:
        123     123     123     123     123     123     123

III.5.6.3. Using STL containers

Containers can be categorised into two groups: sequence containers and associative containers. A property of sequence containers (vector, list, double ended queue: deque) is that the order of their elements is determined by programmers. Associative containers (map, set, bitset etc.) are characterised by the following properties: the order of their elements is determined by the containers themselves and that their elements can be accessed by their key. All containers manage memory space dynamically, that is their number of data can be changed any time.

The member functions of containers helps us manage and access data in them. Since the available function set depends on the type of the corresponding container, it is worth checking in a reference manual (on the Internet) what possibilities a given container can provide. Now, we will only focus on reviewing some generic operations:

  • An element can be inserted (insert()) into or deleted (erase()) from a position to which an iterator points.

  • Elements can be inserted (push) at the beginning of a sequence container (front) or to its end (back), or an element can be both accessed and then removed (pop): push_back(), pop_front() etc.

  • Certain containers can be indexed in the same ways as arrays ([]).

  • The functions begin() and end() returns iterators that can be well used in the case of algorithms. These iterators help us iterate through the elements of data structures.

In the following parts, the things said so far are demonstrated by a code using a container of type vector:

#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
using namespace std;
 
double Sum(const vector<double>& dv) {
   vector<double>::const_iterator p; // constant iterator
   double s = 0;
   for (p = dv.begin(); p != dv.end(); p++)
      s += *p;
   return s;
}
 
bool isOdd (int n) {
    return (n % 2) == 1;
}
 
int main() {
   // output operator
   ostream_iterator<double>out(cout, " ");
   double data[]  = {1.2, 2.3, 3.4, 4.5, 5.6};
 
   // Initialising the vector with the elements of the array
   vector<double> v(data, data+5);
 
   // Printing out the vector
   copy(v.begin(), v.end(), out);  cout << endl;
   cout<<"Sum of the elements: "<<Sum(v)<<endl;
 
   // Adding elements to the vector
   for (int i=1; i<=5; i++)
       v.push_back(i-i/10.0);
   copy(v.begin(), v.end(), out);  cout << endl;
 
   // Adding 4.5 to all elements
   for (int i=0; i<v.size(); i++)
       v[i] += 4.5;
   copy(v.begin(), v.end(), out);  cout << endl;
 
   // Converting all elements to an integer
   vector<double>::iterator p;
   for (p=v.begin(); p!=v.end(); p++)
      *p = int(*p);
   copy(v.begin(), v.end(), out);  cout << endl;
 
   // Deleting each second element
   int index = v.size()-1;
   for (p=v.end(); p!=v.begin(); p--)
      if (index-- % 2 ==0)
         v.erase(p);
   copy(v.begin(), v.end(), out);  cout << endl;
 
   // Sorting the elements of the vector
   sort(v.begin(), v.end() );
   copy(v.begin(), v.end(), out);  cout << endl;
 
   // Searching for 7 in the vector
   p = find(v.begin(), v.end(), 7);
   if (p != v.end() )
       cout << "found"<< endl;
   else
       cout << "not found"<< endl;
 
   // The number of odd elements
   cout<< count_if(v.begin(), v.end(), isOdd)<< endl;
}

The results:

1.2 2.3 3.4 4.5 5.6
Sum of the elements: 17
1.2 2.3 3.4 4.5 5.6 0.9 1.8 2.7 3.6 4.5
5.7 6.8 7.9 9 10.1 5.4 6.3 7.2 8.1 9
5 6 7 9 10 5 6 7 8 9
5 7 10 6 8
5 6 7 8 10
found
2

III.5.6.4. Using STL container adaptors

Container adaptors are containers that modify the container classes above in order to ensure a behaviour that is not their default. Supported adaptors are: stack, queue and priority_queue.

Adaptors have a relatively few member functions and they only provide a specific interface for objects of other container classes. For an example, let's see the class template stack.

The stact, which functions on a "last-in, first-out” basis can be adapted from containers of type vector, list and deque. The adapted stack functions are summarised in the following table:

void push(const value_type& a)

inserting  in the stack,

void pop()

removing the top element of the stack,

value_type& top()

accessing the top element of the stack,

const value_type& top() const

accessing the top element of the stack,

bool empty() const

returns true, if the stack is empty,

size_type size()const

the number of elements in the stack,

operator== and operator<

the operations "equals" and "smaller than".

The following example code uses a stack to convert a number into another numeral system:

#include <iostream>
#include <stack>
#include <vector>
using namespace std;
 
int main() {
   int number=2013, base=16;
   stack<int, vector<int> > istack;
 
   do {
     istack.push(number % base);
     number /= base;
   } while (number>0);
 
   while (!istack.empty()) {
      number = istack.top();
      istack.pop();
      cout<<(number<10 ? char(number+'0'): 
                         char(number+'A'-10));
   }
}