Classes

Aims

To enable students to:

  1. use the struct and class constructs

  2. describe data and function members of a class

  3. access class members

  4. construct and destruct classes

  5. understand this

Use the struct and class constructs

In the first handout classes were described as being a way of grouping data and the code that operates on it into one logical place. In order to achieve this C++ requires a way of describing the data and functions that may be held by a class.

C used the concept of a struct to group several data items (basic and/or user defined) into a single user defined data type. For example a point on a screen may be defined in terms of it offset horizontally (x) and vertically (y) from the top left hand corner of the screen. A struct may be defined to handle this information thus:

struct locn  
{
    int m_x;
    int m_y;
};

Many programmers follow the convention of preceding data members with an m_.

Under C++ structures have been extended to include functions, i.e. a structure can now include both data items and functions to manipulate them. The locn structure above can now include a member function to set the values of x and y:

struct locn
{
    int m_x;
    int m_y;

    void set (int, int);  
};

C++ also introduces a new construct, class. Essentially struct and class are the same, they only differ in their default access rights with members of struct being public by default and those of class, private. The example struct can be represented by the following class:

class Clocn
{
    public:

    int m_x;

    int m_y;

    void set (int, int);  
};

Note, the name has changed form locn to Clocn. Classes under C++ follow the same naming convention as variables (i.e. there are few restrictions), but it is common for class names to be preceded by an upper case C to indicate the name represents a class.

Access rights will be discussed later in this handout.

By convention, a struct is used where only data is aggregated (i.e. there are no manipulation functions) while a class is used where data is aggregated and has associated manipulation functions. In our Clocn example, the early examples without the set function would be represented by a struct, once the function is introduced a class should be employed.

Having defined Clocn in a header file it is necessary to implement its member function in a .cpp file.

#include "locn.h" // Include the file were the class Clocn prototype is defined

void Clocn::set (int x, int y)
{
    m_x = x;
    m_y = y;  
}

Member variables, such as m_x and m_y , appear to member functions as global variables. Changes made in one function to a variable are visible to other functions - there is no need to pass values around via parameters.

After pulling in the prototype of the class (contained in the file locn.h - although it could also be placed directly in the .cpp file) the member function set is defined. This is achieved by entering the name of the class followed by the scope resolution operator (::) and the name of the function. This convoluted syntax allows the compiler to determine which function definition we are providing - remember different classes can implement functions of the same name (polymorphism).

Having defined the class Clocn it is now possible to use it. Classes are treated by the compiler as a new data type (e.g. char, float, int, etc.) so they are declared in the same fashion. The following declares an object position of type Clocn:

void main ()  
{
    Clocn position;  
}

Variables of this new type can now have their member functions called:

position.set (10, 20);

The Clocn class is what is termed a base class - it is not derived from another. It could be that the location class needs to inherit behaviour from another, for example the colour an item at the specified location on the screen should be displayed in. Assuming a class called Ccolour exists, it is possible to modify the definition of the class Clocn to inherit the behaviour defined by the class Ccolour, thus:

class Ccolour  
{
    protected:  
    int m_iColour;

    public:  
    void SetColour (int);  
};

class Clocn: public Ccolour  
{
    public:
    int m_x;  
    int m_y;

    void set (int, int);  
};

Objects of type Clocn now have the ability to set their colour via the SetColour function as in:

Clocn position;

position.SetColour (7);

Describe data and function members of a class

Classes are used to encapsulate data items and their manipulation functions. There is no restriction on the types of data a class can contain, it may be simple data types such as int or char or other more complex ones such as structs and classes - whatever is appropriate for the item being modelled.

To reiterate, a data member of a class is a variable stored within the class, in the Clocn example m_x and m_y are data members. A function member is a function contained within the class, the Clocn class contains the member function set.

Access class members

In order for a class to be useful it must be able to responds to messages. A message flow is indicated by the access of a data or function member. Two methods exist for accessing the items contained within a class.

The . member access operator allows a data or function member of the class to be accessed and manipulated. This syntax permits the compiler to determine which object is being manipulated - objects (even those of the same class) are independent entities with their own data values, hence the compiler needs to know which object is being referenced. The . operator will only work against objects, as in the following:

#include "locn.h" // Include the file were the class Clocn prototype is defined

void main ()  
{
    Clocn position;

    position.set (10, 20);  
}

or

position.m_x = 10;

position.m_y = 2;

When dealing with pointers to objects the access operator is ->. For example in the following program a function is used to manipulate the contents of an object. In order for vManipulatePosition to be able to manipulate the local variable position it must be provided with a pointer to it.

#include "locn.h" // Include the file were the class Clocn prototype is defined

void vManipulatePosition (Clocn *pLocn)  
{
    pLocn->set (10, 20);  
}

void main ()  
{
    Clocn position;

    vManipulatePosition (&position);  
}

Besides being used in tying function definitions to classes, the scope resolution operator :: may be used to specify a specific variable or function. Without qualification the :: permits access to a globally defined function or variable, as in:

#include <iostream.h>

int amount = 123; // A global variable

void main()  
{
    int amount = 456; // A local variable

    cout << ::amount; // Print the global variable

    cout << 'n';

    cout << amount; // Print the local variable  
}

It can also be used to modify which function in a class hierarchy is executed. In the following example the function f is defined in three places, each one is execute in turn.

void f (int i) // global function  
{

...

}

class A // base class  
{  
    public:  
    void f (int i);  
}

class B: public A // derived class  
{  
    public:  
    void f (int i);  
}

void main ()  
{
    A *a;
    B *b;

    ... // create objects

    ::f; // calls global function

    a->f(1); // calls function f in class A

    b->f(1); // calls function f in class B

    b->A::f(1); // calls function f in class A  
}

In order for an object to be able to police its state effectively it needs to be able to control access to the data it contains. A common practice is to hide all data members within the object and only allow access to a few, selected member functions - without such policies it is possible for unexpected behaviour to take place. For example a class representing a current account may have methods to increment and decrement the amount of money held, with the decrement function issuing a warning letter whenever it is called and the balance is negative. If other parts of the system can manipulate the member variable holding the balance without going through the decrement function it is possible that the warning letter is never issued. To control access to member functions and data C++ uses the keywords private, protected and public.

For example the Clocn class may have a function, bVerify, that checks the supplied position is valid (i.e. greater than 0 in both the x and y planes). This function, together with the member variables should be private to the class - i.e. other parts of the program can not access or change them. The following class definition will provide these features:

class Clocn  
{  
    private:      
    int m_x;      
    int m_y;

    bool bVerify (int, int);

    public:      
    void set (int, int);  
};

The public keyword permits free access to the following variables and functions. Any item of code may change them at will. This section stays in effect until the protected, or private keyword is met.

The private keyword prevents code outside of the class - including those that derive from it - from accessing the variables or functions within that section. Again it stays in effect until the protected or public keyword is encountered.

The protected keyword is similar in action to the private, except that classes derived from it have free access to the member functions and variables. Again the effect of this keyword stays in effect until the public or private keyword is encountered.

By default all items within a class are private.

Construct and destruct classes

A constructor is a special function called by the compiler whenever an object is created or defined. Its purpose is to put the object into an executable state - initialise variables, load data from files, etc. If the programmer does not provide a constructor function, the compiler will provide a default implementation - which unfortunately does nothing.

A class may have multiple constructors, but they must be all of the same name as the class. They can take any number of parameters, and as with all functions default their values, but return nothing. Constructors are declared as:

class Clocn  
{  
    private:      
    int m_x;      
    int m_y;

    bool bVerify (int, int);

    public:      
    Clocn ();      
    Clocn (int, int);      
    void set (int, int);  
};

In the above example we have two constructors, one taking no parameters and the other two. Note, no return type is indicated - not even void.

The constructors are defined as follows:

Clocn::Clocn ()  
{
    m_x = 0;
    m_y = 0;  
}

Clocn::Clocn (int x, int y)  
{
    m_x = x;
    m_y = y;  
}

The first initialises the member variables to 0, while the second sets them to whatever values are provided.

A constructor with no parameters is called whenever an instance of the object is declared without parameters, as in:

void main ()  
{
    Clocn position;  
}

If two integers parameters are provided at object creation, as in the example below, the second constructor is called. In this example if any other combination of parameters are provided a compilation error will ensue.

void main ()  
{
    Clocn position (10, 20);  
}

In order to free up any resources used by the object during its lifetime the compiler will call a destructor function whenever it is destroyed. As with constructors, an empty destructor function will be provided by the compiler if none is written by the programmer. Unlike constructors, only one destructor can exist, which takes no parameters and returns no values. A destructor is identified in the same way as a constructor, but with a tilde (~) in front of its name, as in:

class Clocn 
{
    private:  
    int m_x;      
    int m_y;

    bool bVerify (int, int);

    public:     
    Clocn ();      
    Clocn (int, int);      
    ~Clocn ();

    void set (int, int);  
};

Understand this

The variable this is a constant pointer, local to a member function that is automatically provided by the compiler - developers can not declare a variable of this name or assign values to it. It is used by the compiler to point to the instance of an object to which the member function belongs.

Whenever a member function is called the compiler adds a hidden argument to the function that contains the address of this object. For example, the following function call:

Clocn position;

position.set (10, 10);

can be interpreted as:

set (&position, 10, 10);

The hidden argument is available for use within the function as the this pointer, as in:

void Clocn::set (int x, int y)  
{
    // These statements are equivalent
    m_x = x;
    m_y = y;

    this->m_x = x;
    this->m_y = y;  
}

If not mentioned explicitly, the this pointer is used implicitly when manipulating member variables of an object from within a member function. For example, the set function of the Clocn class is defined as:

void Clocn::set (int x, int y)
{
    m_x = x;
    m_y = y;  
}

but is interpreted by the compiler as:

void Clocn::set (int x, int y)
{
    this->m_x = x;
    this->m_y = y;  
}  

Download