Department of Engineering

IT Services

C++ course - Classes

In most old languages you can create variables of pre-determined types (INTEGER, etc) and collect them into arrays.

Languages like C went a stage further, making it possible to bundle together variables of different types into a single entity - a structure. Programmers were no longer restricted to using only the variable types supplied with the language - they could invent their own. But these custom-built types had limitations - for example you couldn't extend the meaning of "+" so that it could work with the custom-built types. In C it's possible to do something like this

   typedef struct Complex {
     float real;
     float image;
     };

If you wanted to add complex numbers together, you'd need to write some functions -

   Complex addComplex(Complex c1, Complex c2) {
      ...
   };

   Complex co1=addComplex(co2,co3);

which gets messy with longer expressions.

Classes lets you go a step further. They're are at the heart of C++'s object-orientation. In the 1B C++ course, classes were created with data in them, also constructors (routines that are run when an object is created) and member functions were created. Complex numbers are supported by the standard library. You can do

   std::cout << c2*(c3+c4);

where the variables are complex. Note that though complex numbers are not part of core C++, they can be used as if they were. You can create your own new types and integrate them into C++ in a similar way. There's much more to classes than that though, as we'll see.

When you design a program the class design is crucial. Simplicity, efficiency and maintainability are important factors. Some older languages give you few modes of working and few design options. If you were using them to write a database containing details about people (their weight, height, name, etc) you might have to have an array for each feature, so the details for a particular person might be contained in weight[237], height[237], name[237], etc. Object-orientated languages let you design more natural programs by creating a person class, with each object containing all the information about a person, as well as ways to process and display the information.

Features

  • Public/Private members - If we are going to produce objects that are like the "lightbulbs" mentioned in the previous talk, we need to make their contents as isolated as possible from the rest of the program. This is done by making data members private if you can. This involves more work but reduces the chance of errors. For example, suppose you were making a database of people. You might start like this
    class person {
    public:
       float height; // height in meters
    }
    
    Now after "person p;" "p.height=1.53;" is possible, but alas, so is "p.height=15.3;" or "p.height=-1.53". This shows the dangers of exposing the inners of an object to the world. One solution is to make height a private member, so that it can't be changed from the outside. This means it can't be read from the outside either. But member functions (i.e. functions that are part of the class) can access these private members, so if these functions are public, the outside world can access the height member in a controlled way via these functions. The following person class has 2 public-accessible functions, one to get the height and one to set it and do some error-checking.

    class person {
    private:
      float height;
    public:
      int set_height(float h)  {
        if (h<0 or h>3) 
          return 0;
        else {
          height=h;
          return 1;
        }
      }
      float get_height() { return height;}
    }
    
    Note how objects are at the centre of things - they have the routines to cope with their own data, data that only they can access. Instead of procedures operating on a global height array, objects are operating on their private height variable.

    This idea of wrapping data up inside objects is very common. For example, in old C code strings were just arrays of characters that required external routines to perform comparisons, etc, but in C++ a string object hides the data behind a set of public routines, including routines that make '=' and '==' work appropriately.

  • Inheritance - The more you use C++ the more you'll be developing classes which require greater sophistication. Often you'll need to add extra functionality to an existing class. C++ provides a mechanism to build new classes from old ones

       class More : public Base {
       int value;
       ...
       };
    

    Here More inherits the members of Base. When an object of type More is created, first the Base constructor is called, then More's constructor. When the object is destroyed, the destructors are called in reverse order. The constructors/destructors are not inherited. Using inheritance you can build up family-trees of related classes. The C++ I/O classes form a tree - see cplusplus pages.
    C++ offers multiple inheritance!

  • Public/Private/Protected inheritance - The public keyword when used with inheritance means that the members of Base are as accessible to More as Base allows. By default derivation is private (which means that even Base's public members can't be accessed by More), but usually derivation should be public, privacy being control by the base class. Look at this program, paying particular attention to the public and private usage.

    class base {
    public:
      int number1;
    };
    
    class kid: public base {
    public:
      int number2;
    private:
      int number3;
    };
    
    int main() 
    {
      kid d;
      base b;
      d.number1=555;
      d.number2=777;
      d.number3=999; 
    }
    

    A line of this code doesn't compile. Let's see why.

    pubpriv.figpublic is being used in 2 ways. We'll deal first with its role in inheritance. The class kid inherits the components of the base. The way it's done here (using public base) means that the privacy of the base components is preserved in kid - i.e. number1 is public in base so it's public in kid too, and thus it can be accessed from outside kid. So d.number1=555; is legal.

    Inside kid, number2 is public and number3 is private. This means that number2 can be accessed from elsewhere but number3 can only be accessed from inside the kid class - from a member function. Consequently d.number2=777; is legal but d.number3=999; is a compile error.

  • Inheritance versus Composition - Rather than a class being derived from another class, a class can use an object of another class as a field. Inheritance is generally used when there is an "is-a" relationship between the 2 classes (a dog is a mammal, so a dog class would be derived from a mammal class), and Composition is used for "has-a" relationships (a cylinder might be a field in an engine class).
  • static - A static data member is part of a class, and exists even before any object of that class is created - there's only one instance of it however many objects of that class are created. This is useful if, for instance, you want to keep a count of how many objects are created. A static data member must be initialised exactly once at file scope (i.e. not within a function). For instance, a static integer four in a class called More could be initialised using int More::four=0;

    You can have static member functions too.

  • const (functions, members) - The value of a const data member can't be changed. A const member function can't change the data of the class.
       int day() const { return d};
    
    doesn't modify state of the class. This is a safety feature (if you accidentally try to change the value of a field, the compiler will tell you) but it's also a performance feature (the compiler might be able to produce faster code if it knows that a routine won't change an object's values).
  • virtual - if a class inherits another class, it also inherits its functions. It's easy to override a base class's function in a derived class, but to fully exploit polymorphism (the ability to deal with many types transparently), a new concept needs to be introduced.

    Suppose that we were designing a graphics editor. We might have

    • a base class Shape which could contain general properties that all shapes which have (float area; perhaps).
    • various derived classes Triangle, Rectangle, etc, each with their own draw member function.
    We'd like to collect these objects together into groups sometimes. An array of pointers to the objects would be appropriate, but what sort of pointers could we use? Fortunately, it's possible to assign a derived class pointer to a base class pointer, so the following is possible, creating an array of pointers.

    Triangle t1, t2;
    Rectangle r1, r2;
    
    vector< Shape* > shapes(4);
    shapes[0]=&t1; 
    shapes[1]=&r1; 
    shapes[2]=&t2; 
    shapes[3]=&r2;
    

    Then we could draw all the shapes in the group by calling the draw function of each element - shapes[0]->draw(), etc. But there's a problem - because the elements are pointers to Shape, it's Shape's draw function which is called, rather than the appropriate derived object's function.

    The solution to this is to define draw in the base class as a virtual function by having something like

       virtual void draw();
    

    which lets us deal with the shapes array without having to worry about the real type of each component; the derived object's draw function will always be called.

    If we want to ensure that the derived objects always have their own draw function we can make shapes's draw function a pure virtual function

       virtual void draw()=0;
    

I suggest that you try compiling, changing and recompiling the code to become familiar with these concepts.

In this example without virtual, a thing pointed to by a Shape* pointer is treated as a Shape. With virtual, the thing pointed to by a Shape* pointer is dealt with according to what it really is.

The shapes array in the example is the kind of thing that a drawing package might use when items are grouped. With virtual you get the best of both worlds - the top-level code doesn't have to worry about unnecessary detail (if a new shape is added to the code, the top-level code won't need to be changed) and each type of shape retains its individuality - each can have its own draw() member function (objects are best-place to do that job - they "know" about themselves).

As you can appreciate, once you've designed the classes (their contents, the visibility of their contents, the class hierarchy), then you've designed much of your program.

Syntax

First design the classes and how they might inherit other classes. Make visible to the outside world only what needs to be visible. Class definitions can be long, but you don't have to put all the code of the member functions inside the class definition. Just put the prototype (also known as the signature) of the member function in the class definition, then you can define the function later. For example you can write

Class little {
int data;
public:
int function(int);
};

int little::function(int number)
{
return data*number;
}

Note that when you define a function within a class definition, that function is inlined (i.e. there's copy of the function in the executable wherever the function is called). This leads to faster but bigger programs. The inline keyword forces a function to be inlined.

The situation becomes more complicated when templates are used too. If you want to see what real C++ code can look like when classes are used, and you have the "g++" compiler, call this program "foo.cc"

#include <string>
using namespace std;
int main() {
  string s;
}

and type

g++ -E foo.cc

The "-E" switch just does the first stage of compilation - reading in the included files etc. Here's some of the output. It uses templates, inheritance, const (in 2 ways) and public in 2 ways)

template <class _Ret, class _Tp>
    class const_mem_fun_t : public unary_function<const _Tp*, _Ret>
    {
    public:
      explicit
      const_mem_fun_t(_Ret (_Tp::*__pf)() const)
      : _M_f(__pf) {}

      _Ret
      operator()(const _Tp* __p) const
      { return (__p->*_M_f)(); }

An illustration

Designing classes to represent various types of CUED students

  • what fields do they share? (factorising)
  • derive from a more general Person? - re-use!
  • functions that access private fields to ensure data-integrity?
  • virtual functions? pure virtuals??

See C++ and Building Classes (a range-limited integer example) for further developments.

Quiz Time

Look at the following code (the line numbers aren't part of the code)

class Base {               [0]
  int one;                 [1]
public:                    [2]
  int two;                 [3]
};                         [4]
                           [5]
class More: public Base {  [6]
  int three;               [7]
public:                    [8]
  static int four;         [9]
  Base base;              [10]
};                        [11]
                          [12]
int main() {              [13]
                          [14]
More more;                [15]
more.one=0;               [16]
more.two=0;               [17]
more.three=0;             [18]
more.four=0;              [19]
more.base.two=0;          [20]
}                         [21]
  1. The More class uses the Base class in 2 ways. What are those ways called? Virtual and Real Inheritance and Composition Static and Public
  2. Why doesn't line 16 compile? Because one is private Because one is inherited from Base Because more.one doesn't exist.
  3. Why doesn't line 18 compile? Because three is private Because three isn't inherited from Base Because more.three doesn't exist.
  4. Line 19 doesn't compile because more.four doesn't exist. Which of the following would set four to 0. more.base.four=0; More.four=0; More.base.four=0; None of these

Exercises

  • Some questions to ask yourself
    • What's the difference between an object and a class?
    • Members of a class can be public, protected or private and so can inheritance (as shown in the example). Are you happy with how these 2 factors combine to control the visibility of the base class member in the derived class?
    • What's the point of having const functions?
    • Why might private member functions be useful?
  • Without writing any code, sketch out how you might create classes to represent Points, Triangles, Rectangles and Squares. How might the class hierarchy be extended to deal with Cuboids, Cubes and Prisms?
  • Write a program to show the order in which constructors and destructors are called in a hierarchy of derived classes.
  • Create a class containing 2 integers, then create an object of that class. See if the address of the object is the same as the address of one of the fields.
  • Write the code to simulate a remote controlled toy car. Make a toy car class that stores the position, direction and speed of the car. Write a constructor that lets the position and direction be initialised. Write a simple set of member functions for the car (e.g. forwards(int seconds), spin(int degrees)), including a routine that prints the current position and direction. Then write a set of instructions to test the car.