Department of Engineering

IT Services

Classes

Classes 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.

fileptr.cc is an example of a simple class built from scratch. Just as local variables of type int are destroyed when no longer required, so are local objects of user-created classes. The destructor (the routine that's run when an object is destroyed) can be defined to do extra work.

This class has 2 constructors (one for the situation when it's given 2 strings, and the other when it's given a pointer to an open file). It also has a destructor that's called when the object goes out of scope (when the routine it's created in ends, for example). Here, the destructor closes the file automatically.

   class File_ptr {
   FILE *p;
   public:
   // 2 constructors
   File_ptr (const char* n, const char* a) { p=fopen(n,a); }
   File_ptr (FILE *pp) {p=pp;}

   // destructor
   ~File_ptr() {fclose(p);}
   // redefinition of (), providing a way to access the file pointer
   operator FILE* () {return p;}
   };


   void use_file(const char* fn)
   {
   File_ptr f(fn,"r");
   // file will be closed when f goes out of scope
   // ...
   }

Derived classes

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. The public keyword 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.

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, but you can say

   More::More(int sz):Base(sz){}

passing arguments to a base member class constructor (or to a number of constructors using a comma-separated list).

A derived class can be assigned to any of its public base classes, so the following is possible

   class More : public Base {
   int value;
   ...
   };
   Base b;
   More m;
   b=m;

It will fill the fields of b with the corresponding values in m. However, m=b isn't possible.

You can derive new classes from a derived class, and you can derive many different classes from a base class so you can develop a family tree of related classes. As we'll see soon, the ``parent'' classes can keep parts of themselves private, not only to unrelated classes but to derived classes too.

Big classes don't necessarily imply big objects: all objects of a class share member functions, for instance.

It's possible for a class to be derived from 2 or more other classes. It's called Multiple Inheritance but it introduces complications, so don't use it unless you know what you're doing.

Friend classes and functions

It is sometimes useful to let the functions of one class have access to the components of another class without making the components public and without the overhead of having to call member functions to get private data.

The following shows how one class (in this case Another) can let another class (in this case Base) be a ``friend'' class

class Another {
friend class Base;
int value;
...
}

For the sake of clarity it's a good idea to have the friend lines at the start of the class definition. Note that friendship is granted by the class being accessed, not claimed by the class wanting access.

A friend may be a non-member function, a member function of another class, or an entire class. Friends and derived classes differ in that derived classes might not be able to access all the members of the base class.

Class member privacy

Class members can have 3 types of privacy

private
- can be used only by member functions and friends
protected
- like private except that derived classes have access to them too. Try not to use this - it can help performance but it breaks encapsulation.
public
- available to any function.

By default class members are private. You often want the data to be private (so that they can't be tampered with from the outside) but the functions public (so that they can be called by other objects). If you want other objects to be able to change the data, write a member function that the objects can call and make sure that the member function does validity checking. Then other member function that use the data won't have to check for validity first.

A private function is often called a helper or utility function because its for the benefit of other member functions. Note that a member function

1.
can access private data
2.
is in scope of class
3.
must be invoked on an object

If you have the choice of writing a friend or member function, choose a member function.

Note that structs are just classes which default to public members and public inheritance. By convention they're used (if at all) for data-only classes.

Static members

A static member is part of a class, but not part of an object of a class, so there's only one instance of them 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 member function

1.
can access private data
2.
is in scope of class
3.
but can't be invoked on an object - objectname.staticmemberfunction() can't access any object-specific data.

Const 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.

Static Const members

Details regarding how to initialise static const members changed late in the C++ specification. A static const data member of integral or enum type can be initialized in the class definition, but you still need to provide another definition (without an initializer) for it. So

class C {
  const static int csi = 5;
};

const int C::csi;

is legal, but you can't do the same for floats, non-statics or non-consts. With some old compilers you have to initialise outside the class.

Overriding behaviour

As well as adding members a derived class can override behaviour simply by redefining a member function. For instance, if you are developing objects for a graphics editor, you'd like each kind of object to have a draw function, but you'd like each kind of object to have its own version.

Later (section 11) more details of class derivation will be described.