An Introduction to C++ Programming - Part 6

Introduction

This month's article is dedicated to the buzzword "inheritance." Inheritance is a way of expressing similarity. As an example, we all have an idea of what a chair is, despite that there are many totally different kinds of chairs. This similarity can be expressed with inheritance. Before delving into inheritance, though, let's finish where we left last month, and take care of formatting output for our own types.

What do we want?

We're now facing a tough situation. There are a number of formatting parameters, and we must know how to handle every single one of them. For example, say we're to print our Range left aligned with a field width of 20 characters, a padding character of '.' and showing also the positive sign. How do we do this? To begin with, what appearance do we want? One thing is for sure, whoever wants to print our Range with that formatting, expects it to be valid for the Range itself, and not for the first '[' or upper bound of the Range only. In other words, we must see to it that all of our range gets a field width of 20 characters, right aligned. Of course, since we cannot really know how wide the upper and lower limit will be when printed, we cannot solve the problem. Tough indeed. OK, let's do second best. Whatever the width is set to, we'll occupy, and we'll give the upper and lower limit equally much space.

  ostream& operator<<(ostream& os, const Range& r)
  {
    if (!os.opfx()) return os;

    int width=os.width(); // get current width setting.
    os << setw(1) << '[' << setw(width) << r.upperBound()
       << ',' << setw(width) << r.lowerBound() << ']';
    os.osfx();
    return os;
  }
In the above we make use of the fact that width is cleared after printing. Not optimal, given our wishes, but it's OK. Now for the topic of this month.

Inheritance

Let's have a look at a classic problem, with a classic Object Oriented solution. We've been contracted by Big Company, to write software for their staff related issues. At Big Company, we find managers, engineers, secretaries and project leaders (hmm, looks like where I work, except for the lack of marketers.) Managers manage a number of employees and have access to a secretary. A project leader reports to the manager responsible for his project. An engineer works on a project and thus reports to a project leader. Every engineer also has a manager. Now, how do we model this?
We recognise one thing for sure. Every employee has a name and a manager. Now let's make a simple minded attempt:

  class Manager {
  public:
    Manager(const char* aName,
            const Manager* manager,
            Secretary* aSecretary);
    const Manager* manager(void) const;
    const char* name(void) const;
    Secretary* secretary(void);
  private:
    char* theName;
    const Manager* theManager;
    Secretary* secretary;
  };

  class Engineer {
  public:
    Engineer(const char* aName,
             const Manager* aManager,
             const ProjectLeader* aProjectLeader);
    const Manager* manager(void) const;
    const ProjectLeader* projectLeader(void) const;
    const char* name(void) const;
  private:
    char* theName;
    const Manager* theManager;
    const ProjectLeader* theProjectLeader;
  };

  class Secretary {
  public:
    Secretary(const char* aName,
              const Manager* aManager);
    const Manager* manager(void) const;
    const char* name(void) const;
  private:
    char* theName;
    const Manager* theManager;
  };

  class ProjectLeader {
  public:
    ProjectLeader(const char* aName,
                  const Manager* aManager);
    const char* name(void) const;
    const Manager* manager(void) const;
  private:
    char* theName;
    const Manager* theManager;
  };
One problem here should be apparent. All employees will have identical code for handling name and manager. That's bad. Duplicated code is always bad. What's worse, this problem itself will duplicate. Everything we want to be able to do to any employee, must be written for the four employee types. Sure, a template can help, but it will not be the solution. What if, we could instead express within the programming language, that we have something called employee, and that employees have a name and a manager. If we could then say that a manager is an employee, but with a secretary. Likewise we could model that an Engineer is an employee, but with the extras that they have project leaders. The case for the secretary and project leader is of course analogous. This is what inheritance is all about. We create a class employee, with all the things that are common to all kinds of employees, and then we let the other classes inherit from it, and add only the extras.

An example

We can write the Employee class like this:

  class Manager; // Forward declaration

  class Employee {
  public:
    Employee(const char* aName, const Manager* aManager);
    const char* name(void) const;
    const Manager* manager(void) const;
  private:
    const char* theName;
    const Manager* theManager;
  };
On the line marked "Forward declaration" we say that there is a class called Manager. That's really all we say. Since we've said that there is such a class, we are allowed to use pointers to the class, and that's needed in the Employee class. Since there's a circular dependency between Employee and Manager, a forward declaration is needed. Note that it is not possible to instantiate an object of a forward declared class. The class must be defined before you can instantiate objects of it, but you can declare and define pointers and references to forward declared types. You can even declare functions accepting and returning objects of forward declared types, but you cannot define nor call the function before the type's definition is known. Now let's define the manager class, by inheriting from Employee:

  class Secretary; // Forward declaration

  class Manager : public Employee {
  public:
    Manager(const char* aName,
                   const Manager* aManager,
                   Secretary* aSecretary = 0);
    void setSecretary(Secretary* aSecretary);
    Secretary* secretary(void);
  private:
    Secretary* theSecretary;
  };
Can you feel something cool going on here? When we declare Manager as "public Employee", we say that a manager is, for all intents and purposes, an employee. Everything you can do to an employee, you can do to a manager (OK, so the model isn't 100% realistic.) Everything that is public in "Employee" is public in "Manager" as well (except for the constructor and a few other special member functions.) That is, it's legal to call the member function "name" for a manager, and when you do, it's the member function defined in "Employee" that is executed. That is, what we have done is to define a new type "Manager" that is an extension of the type "Employee." Cool eh? The member function "setSecretary" is needed, since the first manager would other wise never be assigned a secretary (you can have a manager without a secretary, but not a secretary without a manager, thus when the company first starts as a one man business, there is no secretary, but one is hired later.) Let's add the Secretary, Engineer and ProjectLeader and then implement them all:

  class Secretary : public Employee
  {
  public:
    Secretary(const char* aName,
              const Manager* aManager);
  };

  class ProjectLeader : public Employee
  {
  public:
    ProjectLeader(const char* aName,
                  const Manager* aManager);
  };

  class Engineer : public Employee
  {
  public:
    Engineer(const char* aname,
             const Manager* aManager,
             const ProjectLeader* aProjectLeader);
    const ProjectLeader* projectLeader(void) const;
  private:
    const ProjectLeader* theProjectLeader;
  };

  Employee::Employee(const char* aName,
                     const Manager* aManager)
    : theName(aName),
      theManager(aManager)
  {
  }

  const char* Employee::name(void) const
  {
    return theName;
  }

  const Manager* Employee::manager(void) const
  {
    return theManager;
  }

  Secretary::Secretary(const char* aName,
                       const Manager* aManager)
    : Employee(aName, aManager) //****
  {
  }

  ProjectLeader::ProjectLeader(const char* aName,
                               const Manager* aManager)
    : Employee(aName, aManager) //****
  {
  }

  Engineer::Engineer(const char* aName,
                     const Manager* aManager,
                     const ProjectLeader* aProjectLeader)
    : Employee(aName, aManager), //****
      theProjectLeader(aProjectLeader)
  {
  }

  const ProjectLeader* Engineer::projectLeader(void) const
  {
    return theProjectLeader;
  }

  Manager::Manager(const char* aName,
                   const Manager* aManager,
                   Secretary* aSecretary)
    : Employee(aName, aManager), //****
      theSecretary(aSecretary)
  {
  }

  void Manager::setSecretary(Secretary* aSecretary)
  {
    theSecretary = aSecretary;
  }

  Secretary* Manager::secretary(void)
  {
    return theSecretary;
  }
The "Employee" implementation is familiar. The constructors of the other classes is the only somewhat odd thing. In the initialiser list of the constructors we call the constructor of "Employee" on the lines marked //****. Since a Manager (for example) is an Employee, the employee side of the Manager must be constructed, and it is done with an explicit call to the constructor of the ancestor. This is actually all there is to it. Here's proof!

  int main(void)
  {
    Manager CEO("Big Boss", 0);
    Secretary sec1st("1st secretary", &CEO);
    CEO.setSecretary(&sec1st);

    Secretary shared("shared secretary", &CEO);

    Manager middle1("Medium boss 1", &CEO, &shared);
    Manager middle2("Medium boss 2", &CEO, &shared);


    ProjectLeader p("Proj1", &middle1);
    Engineer e("Eng", &CEO, &p); // Managed by CEO, but
                                  // work on a project
                                  // controlled by p.

    const Manager* pm = CEO.manager();
    cout << "CEO is :" << CEO.name()
         << " whose manager is "
         << (pm ? pm->name() : "nobody") << endl;
    cout << "The secretary of CEO is "
         <<  CEO.secretary()->name() << endl;
    cout << CEO.secretary()->name() << "'s manager is "
         << CEO.secretary()->manager()->name() << endl;

    cout << "The name of middle1 is: " << middle1.name()
         << " whose secretary is "
         << middle1.secretary()->name() << endl;
    cout << "The manager of "
         << middle1.secretary()->name() << " is "
         << middle1.secretary()->manager()->name() << endl;
    cout << p.name() << " is a project leader managed by "
         << p.manager()->name() << endl;
    cout << e.name() << " is an engineer managed by "
         << e.manager()->name()
         << " and works on a project controlled by "
         << e.projectLeader()->name() << endl;
  }
When executed, I get this output:

  [d:\cppintro\lesson6]staff.exe
  CEO is :Big Boss whose manager is nobody
  The secretary of CEO is 1st secretary
  1st secretary's manager is Big Boss
  The name of middle1 is: Medium boss 1 whose secretary is shared
  secretary
  The manager of shared secretary is Big Boss
  Proj1 is a project leader managed by Medium boss 1
  Eng is an engineer managed by Big Boss and works on a project
  controlled by Proj1
Not bad eh? Inheritance is a way good way of expressing commonality. To make it even neater, let's create a print operator for "Employee" and use that in "main".

  ostream& operator<<(ostream& os, const Employee& e)
  {
    if (!os.opfx())
      return os;

    cout << '"' << e.name() << "\" is managed by ";
    const Manager* pm = e.manager();
    if (pm) {
      cout << '"' << pm->name() << '"';
    } else {
      cout << "nobody";
    }
    os.osfx();

    return os;
  }

  int main(void)
  {
    Manager CEO("Big Boss", 0);
    Secretary sec1st("1st secretary", &CEO);
    CEO.setSecretary(&sec1st);

    Secretary shared("shared secretary", &CEO);

    Manager middle1("Medium boss 1", &CEO, &shared);
    Manager middle2("Medium boss 2", &CEO, &shared);


    ProjectLeader p("Proj1", &middle1);
    Engineer e("Eng", &CEO, &p); // Managed by CEO, but
                                  // work on a project
                                  // controlled by p.

    const Manager* pm = CEO.manager();
    cout << "CEO is :" << CEO.name()
         << " whose manager is "
         << (pm ? pm->name() : "nobody") << endl;
    cout << "The secretary of CEO is "
         <<  CEO.secretary()->name() << endl;
    cout << CEO.secretary()->name() << "'s manager is "
         << CEO.secretary()->manager()->name() << endl;

    cout << "The name of middle1 is: " << middle1.name()
         << " whose secretary is "
         << middle1.secretary()->name() << endl;
    cout << "The manager of "
         << middle1.secretary()->name() << " is "
         << middle1.secretary()->manager()->name() << endl;
    cout << p.name() << " is a project leader managed by "
         << p.manager()->name() << endl;
    cout << e.name() << " is an engineer managed by "
         << e.manager()->name()
         << " and works on a project controlled by "
         << e.projectLeader()->name() << endl;


    cout << endl;

    cout << CEO << endl;
    cout << sec1st << endl;
    cout << middle1 << endl;
    cout << middle2 << endl;
    cout << shared << endl;
    cout << p << endl;
    cout << e << endl;
  }
The output now becomes:

  [d:\cppintro\lesson6]staff2.exe
  CEO is :Big Boss whose manager is nobody
  The secretary of CEO is 1st secretary
  1st secretary's manager is Big Boss
  The name of middle1 is: Medium boss 1 whose secretary is shared
  secretary
  The manager of shared secretary is Big Boss
  Proj1 is a project leader managed by Medium boss 1
  Eng is an engineer managed by Big Boss and works on a project
  controlled by Proj1

  "Big Boss" is managed by nobody
  "1st secretary" is managed by "Big Boss"
  "Medium boss 1" is managed by "Big Boss"
  "Medium boss 2" is managed by "Big Boss"
  "shared secretary" is managed by "Big Boss"
  "Proj1" is managed by "Medium boss 1"
  "Eng" is managed by "Big Boss"
Can you see what happens here? As mentioned last month, "operator<<" is a function, with the syntax of an operator. This function is defined for "const Employee&" only, and it works with "Secretary" and "Manager" as well. Since "Manager" and "Secretary" publicly inherit from "Employee", they can be used as "Employee", so a reference to an "Employee" can legally refer to a "Secretary" or a "Manager." While this is neat, it's not over by a long shot.

Virtual functions

Since the different classes hold somewhat different information (the derived classes are more specialised, so they hold more specific information,) it would be nice if we could see the differences when printing. One way of doing this is, of course, to define operator<< for all classes, but that's cheating. We'll do better than that by using object orientation, or more specifically, something called "dynamic binding" which is very central to object orientation. Say we use the template stack from part 4, and instantiate a stack of pointers to employees. Since a pointer to an employee can actually point to a secretary, a manager, a project leader, an engineer, or some other weird kind of employee we haven't yet defined, say a human resources person or (shudder) a marketer. Still, if we wanted to print the employees pointed to by the stack, wouldn't it be neat if we could see exactly what there was to see, for example that the employee happened to be an engineer, and allow us to see the engineer's project leader? Hold on tight now, here comes a mini example showing exactly that kind of thing:

  #include 

  class A
  {
  public:
    virtual void print(ostream&); //** 1
  };

  void A::print(ostream& os)
  {
    os << "A";
  }

  class B : public A
  {
  public:
    virtual void print(ostream&); //** 2
  };

  void B::print(ostream& os)
  {
    os << "B";
  }

  class C : public A
  {
  public:
    void print(ostream&); //** 3
  };

  void C::print(ostream& os)
  {
    os << "C";
  }

  class D : public A
  {                    //** 4
  };

  class E : public B
  {
  public:
    virtual void print(ostream&);
  };

  void E::print(ostream& os)
  {
    os << "E : public ";
    B::print(os); //** 5
  }

  ostream& operator<<(ostream& os, A& a)
  {
    a.print(os);
    return os;
  }

  int main()
  {
    A a;
    B b;
    C c;
    D d;
    E e;
    cout << a << endl;
    cout << b << endl;
    cout << c << endl;
    cout << d << endl;
    cout << e << endl;
    return 0;
  }
When executed, this program displays:

  [d:\cppintro\lesson6]virt.exe
  A
  B
  C
  A
  E : public B
How did this work? Let's first have a look at the marked lines in the source code. At **1, we declare the member function "A::print" as "virtual." "virtual" means, that if the function is overloaded by a descendant (any of the other classes in the example,) and the member function is called on an object of the descendants class (say B,) but through a pointer or reference to the ancestor (that is A,) it's the function of the descendant (say B again) that is to be called. At **2 this kind of overloading takes place the way I think it should be. As can be seen at **3, the keyword "virtual" is not needed when overloading (if a member function is virtual for an ancestor, they automatically become virtual for the descendents.) I still think it's a good idea to have the keyword there, because it makes the intention clearer.
At **4 there is no function overloaded, so if d.print() is called, it's A::print() that's executed (it is, however, possible to inherit from D and overload "print", and it would behave as the other examples. There's no way to "unvirtualise" a member function.
At **5 the "print" of the immediate ancestor (B) is called.
With the help of the above, let's analyse the program execution.
  • "cout << a" creates a reference to "a" and calls "print" on it. Pretty straight forward.
  • "cout << b" creates a reference to "b" (but the reference is an "A&") and calls "print" on it. Since the object referenced really is a "B", and the member function "print" is virtual and overridden for class "B", it's "B::print" that's called.
  • "cout << c" creates a reference to "c" (an "A&" to "c") and calls "print" on it. The situation is the same as for "b".
  • "cout << d" does likewise, but since there is no "D::print", it's "A::print" that's called.
  • "cout << e" calls "print" for an "A&" to "e", and since class "E" overrides "print", it's that "print" that's called. It writes "E : public" and then calls the "print" of "B".
Are you ready for something mind-stretching? With the aid of the above, you hardly ever need a "switch" statement. As a matter of fact, whenever you have a "switch" statement in C++, think carefully if the problem couldn't be solved with inheritance and virtual functions instead. Usually the answer is not only yes, but it even makes for a solution that's easier to understand. Note the differences between this virtual function call, or dynamic binding as it is also called, and templates. Templates generate code at compile time, fixed code, in several instances. Here there is only one "operator<<", it's not a template. It calls the virtual function, which dynamically, at run-time, is bound to a function of the object referred to.
Now that you've seen this, it's time for a Very Important Rule. Whenever you use inheritance, make sure you *always* declare the destructor of the base class "virtual." Here's a mini example showing you why:

  #include 

  class A
  {
  public:
    ~A();
  };

  class AA : public A
  {
  public:
    ~AA();
  };

  class B
  {
  public:
    virtual ~B();
  };

  class BB : public B
  {
  public:
    virtual ~BB();
  };

  A::~A() { cout << "~A" << endl; }
  AA::~AA() { cout << "~AA" << endl; }
  B::~B() { cout << "~B" << endl; }
  BB::~BB() { cout << "~BB" << endl; }

  int main()
  {
    A* pa1 = new A;
    A* pa2 = new AA;
    B* pb1 = new B;
    B* pb2 = new BB;
    delete pa1;
    cout << "--" << endl;
    delete pa2;
    cout << "--" << endl;
    delete pb1;
    cout << "--" << endl;
    delete pb2;
    cout << "--" << endl;
    return 0;
  }
The execution results in:

  [d:\cppintro\lesson6]virt2
  ~A
  --
  ~A
  --
  ~B
  --
  ~BB
  ~B
  --
As you can see, the destructor for "AA" is never called. The reason is that we're dealing with pointers to the base classes only, and when calling delete on a pointer to an object, the destructor for the object pointed to is called. The destructor to call is determined by the type of the pointer, and if the destructor isn't declared "virtual," it won't call the most derived version, as it should. The above result also gives a reason to switch to the next issue with inheritance.

Construction and Destruction

Let's revisit the old "Tracer" class from part 2. It looks like this:

  class Tracer
  {
  public:
    Tracer(const char* tracestring);
    ~Tracer(); // destructor
  private:
    const char* string;
  };

  Tracer::Tracer(const char* tracestring)
    : string(tracestring)
  {
    cout << "+ " << string << endl;
  }

  Tracer::~Tracer()
  {
    cout << "- " << string << endl;
  }
With the aid of the tracer, we can see what happens with object construction and destruction when inheritance is used. Let's go for an example right away:

  class A : public Tracer
  {
  public:
    A(const char* name1, const char* name2)
     : Tracer(name1),
       trc(name2) { cout << "A" << endl;}
    virtual ~A() { cout << "~A" << endl;}
  private:
    Tracer trc;
  };

  class B : public A
  {
  public:
    B(const char* n1, const char* n2, const char* n3)
      : A(n1, n2), trc(n3) { cout << "B" << endl;};
    virtual ~B() { cout << "~B" << endl;}
  private:
    Tracer trc;
  };

  int main(void)
  {
    cout << "creating an A" << endl;
    A a("A-ancestor", "A-component");
    {
      cout << "creating a B" << endl;
      B b("B-A-ancestor", "B-A-component", "B-component");
      cout << "destroying a B" << endl;
    }
    cout << "destroying an A" << endl;
    return 0;
  }
Execution gives me this printout:

  creating an A
  + A-ancestor
  + A-component
  A
  creating a B
  + B-A-ancestor
  + B-A-component
  A
  + B-component
  B
  destroying a B
  ~B
  - B-component
  ~A
  - B-A-component
  - B-A-ancestor
  destroying an A
  ~A
  - A-component
  - A-ancestor
An analysis shows that when creating an object, the first thing is that the data members of the base class are created, then the constructor body of the base class is executed. After that, the data members of the derived class is created, followed by the execution of the constructor, and so it goes towards the most derived class. The last thing to be executed is the constructor body of the most derived class. This is out of necessity. When the constructor body for the most derived class executes, everything it might need access to (data members, as well as the inherited parts,) is already constructed and legal to use. Note an implication of this: It's not a very good idea to call virtual functions in a constructor (as a matter of fact, if called from within a constructor they don't have their "virtuality", binding is static.) As usual in C++, destruction is in exactly the reverse order of construction. Hmm... There's a lot more to say on the topic, but I think I'll save some for next month.
Oh, OK, one last thing. WARNING!!! *Never* use public inheritance as a way of reusing code. Public inheritance models "is-a" relationships only. If you use public inheritance for the purpose of reusing code, you're creating a maintenance nightmare for yourself, as well as conceptual havoc in your design. Please, please, take note of this. It's probably the most frequently committed sin in C++ and any other object oriented programming language, and it brings you nothing but trouble. Why? Even if your intention is code-reuse only, you will in fact, whether you like it or not, get an "is-a" relationship with public inheritance. Let's say that we in the staff example defined the class project, and we know that all projects are named. Let's also say that to make life easy, we re-use code from the "Employee" class, by publicly inheriting from it. Now we'll be able to do amazing things with the projects! Public inheritance is for "is-a" relationships only.

Exercises

  • What's the difference between inheritance and templates?
  • Say we state firm pre and post conditions for a virtual function. In what way, if any, may the pre and post conditions for an override in a derived class differ from that in the base class (this truly requires some thought.)
  • Experiment with the constructor/destructor tracer and exceptions. What happens?
  • Expand the employee example such that the operator<<(ostream&, const employee&) prints more detailed data depending on the kind of employee. You're not allowed to use templates.
  • Why is it important to declare destructors virtual?
  • In what way can dynamic binding replace switch statements?
  • The word "public" when inheriting suggests that there might be other kinds of inheritance. What might those be, and what would the difference be?
  • If we have a class A, and a class B that publicly inherits from A, an instance of A can call a member function of B. How?
  • An often heard prejudicism that's totally wrong, is that virtual function calls are slow. Where do you think this idea stems from, and why is it wrong?

Recap

For being such a seemingly small topic, lots of new and fairly advanced things have been seen:
  • Public inheritance can be used to extend existing types, such that the extension can still be used just like the type being extended from.
  • Public inheritance models "is-a" relationships (and "is-a" relationships only.)
  • Dynamic binding is a way to call a function that is determined by the run-time type of the object referred (or pointed) to.
  • Dynamic binding can often replace switch statements.

An Introduction to C++ Programming - Part 5

Introduction

We've seen how the fundamental types of C++ can be written to the screen with "cout << value" and read from standard input with "cin >> variable". This month, you will learn how you can do the same for your own classes and structs. It's surprisingly easy to do.

Exploring I/O of fundamental types

Formatted I/O, is not part of the language proper in C++ (or in C for that matter.) It's handled by an I/O library, that's implemented in the language. (If you're familiar with Pascal, try to implement something like Write and WriteLn in Pascal. You can't, the language doesn't allow it, that's why it's built into the language itself.) We've seen a number of times how we can print something with "cout << value". How can this be expressed in the language? To begin with, the syntax is legal only because you can overload operators in C++. You've already seen that with operator=. Let's see what actually happens when we use operator=.

  class X
  {
  public:
    ...
    X& operator=(int i);
    ...
  };

  X x;

  x=5; //**
At the last line of the example, what actually happens is that operator= is called for the object named "x". Another way of expressing this is:

  x.operator=(5);
In fact, this syntax is legal, and it generates identical code, because this is how the compiler will treat the more human-readable form "x=5".
As we can see then, an operator overridden in a class, is just like any other member function, it's just called in a peculiar form.
Let's go back to printing again. "cout" is an object of some class, which has operator<<(T) overloaded, where T is any of the fundamental types of C++. The relevant section of the class definition looks as follows:

  class ostream
  {
    ...
  public:
    ...
    ostream& operator<<(char);
    ostream& operator<<(signed char);
    ostream& operator<<(unsigned char);
    ostream& operator<<(short);
    ostream& operator<<(unsigned short);
    ostream& operator<<(int);
    ostream& operator<<(unsigned int);
    ostream& operator<<(long);
    ostream& operator<<(unsigned long);
    ostream& operator<<(float);
    ostream& operator<<(double);
    ostream& operator<<(long double);
    ostream& operator<<(const char*);
    ...
  };
The value returned by each of these is the stream object itself (i.e. if you call "operator<<(char)" on "cout", the return value will be a reference to "cout" itself.)
With the above in mind, we can see that writing

  int i;
  double d;

  cout << i << d;
is synonymous with

  int i;
  double d;
  (cout.operator<<(i)).operator<<(d);
The only difference for reading is that the class is called "istream" instead, and that the operator used is operator>>().

I/O with our own types

The most important thing to recognise is that our own types (classes and structs) always consists of fundamental types. This is important. The C++ I/O library only supports I/O of the fundamental types, so if our own data types consisted of something completely different, I/O would be very difficult indeed.
So, how do we make sure we can do I/O on ranges and stacks (from the earlier lessons?) What about extending our own class with the members operator<< and operator>>? This would, sort of, work, but the syntax would change. As I wrote above, "a << b" is identical with "a.operator<<(b)", and if we add operators << and >> to our class, we'll require our object on the left hand side, and the stream to print on/read from, on the right hand side, and that's not what we want. Another possible way of doing this is to edit the ostream and istream class to contain operator<< and operator>> for our own classes. Does that seem like a good idea to you? It doesn't to me.
The solution does yet again lie in operator overloading, but this time in a somewhat different way. We just saw how we can overload an operator for a class, such that the operator becomes a member function for that class (only, in its use, the syntax differs.) It's also possible to overload operators, such that the operator becomes a function, provided that at least one of the parameters to the operator is not a built-in type. Most operators that can be defined like a nonmember function, accept two parameters. Such is the case for our new friends operator<< and operator>>.
Let's revisit our old friend, the class "Range." This is the definition of "Range", for those who do not have old issues handy (I've added "const" on the member functions, now that you know what it's for. See part 3 for details if you've forgotten):

  struct BoundsError {};
  class Range
  {
  public:
    Range(int upper_bound = 0, int lower_bound = 0)
    throw (BoundsError);
    // Precondition: upper_bound >= lower_bound
    // Postconditions:
    //   lower == upper_bound
    //   upper == upper_bound

    int lowerBound() const throw ();
    int upperBound() const throw ();
    int includes(int aValue) const throw ();
  private:
    int lower;
    int upper;
  };
How should this thing be printed and read? Here's a wishlist. We'll reduce it a little bit, to be more realistic later.
  1. The syntax and semantics for printing must be the same as for the fundamental types of C++.
  2. Full commit or roll back, that is, either we print all there is to be printed, or we print nothing at all.
  3. The print must be in a form distinguishable from, say, two integers separated by a comma.
  4. Full type safety
  5. Encapsulation not violated.
  6. No unnecessary computations.
  7. We want printing and reading synchronized (i.e., if we read something, then print a range, then reads something, we want the reading to complete before printing, and we want it all printed before reading again.) Since both reading and writing is normally buffered, it is not at all obvious that this will occur.
All of these are possible, but #2, #6 and #7 are usually skipped. I'll skip #2 for now. What's the appearance we want of a range when printed, and what format should we accept when reading? A golden rule in I/O (and not just in C++) is to be very strict in your output, but very liberal in what you accept as input. Normally, the C++ I/O library handles just exactly this for you. For format I chose is "[upper,lower]", no spaces anywhere. On input however, white space is allowed before the first bracket, and between any of the tokens (the tokens here are '[', number, ',' and ']').
OK, so now we have a pretty good picture on what to do, now... how? Overloading operator<< as a global function. The signature becomes:

  ostream& operator<<(ostream&, const Range&);
This declares a function, which has the syntax of a left shift operator. If we have code like:

  Range r;
  int i;
  ...
  cout << r << i;
The compiler will treat it as

  operator<<(cout, r).operator<<(i);
This even works for more complex expressions, like:

  Range r;
  int i;
  int j;
  ...
  cout << i << r << j;
Which the compiler interprets as:

  operator<<(cout.operator<<(i),r).operator<<(j);
Study these examples carefully, to make sure you understand what's going on. Now, after these examples, it's fairly easy to get down to work with implementing the operator<< function.

  ostream&A& operator<<(ostream& os, const Range& r)
  {
    os << '[' << r.upperBound()
      << ',' << r.lowerBound() << ']';
    return os;
  }
Here "r" is passed as const reference, since the function does not alter "r" in any way (and promises it won't.) The stream, "os", however, is passed by non-const reference. This is essential. Printing does alter a stream. It will not be the same after printing as it was before printing. It is not possible to pass it by value, since when passing by value, means copying, and copying a stream doesn't make much sense (think about it.) Inside the function, we're printing known types, char and int, so the operator<< provided by the I/O class library suits just fine. How well does this suit the 7 points above? The syntax is correct, and the semantics are too, given the facts known this far (more is needed, as you will see further down.) We do not have full commit or rollback, but I mentioned already in the beginning that we'll skip that for now. The format is distinct enough, we have type safety and encapsulation is not violated. This is as far as most books on C++ cover when it comes to printing your own types. However, we do make some unnecessary computations if the stream is bad in one way or the other. Say, for example, we have a detached process. Detached processes do not have standard output and standard input (unless redirected) and as such printing will always fail. Why then, even try? We also do not synchronize our output with input. The check and synchronization is simple to make, but oddly enough not mentioned in most C+ books.

  ostream& operator<<(ostream& os, const Range& r)
  {
    if (!os.opfx())
      return os;
    os << '[' << r.upperBound()
      << ',' << r.lowerBound() << ']';
    os.osfx();
    return os;
  }
The "prefix" ("opfx" means "output prefix") function checks for a valid stream, and also synchronizes output with input. The "suffix" ("osfx" means "output suffix") signals end of output, so that synchronized input streams can begin accepting input again.) I dare you to find this in a C++ book (I know of one book.) I don't know why it's just about always skipped, since it isn't more difficult than this to avoid unnecessary computations and synchronize input with output. That was printing, how about reading? The signature and general appearance of the function is pretty much clear from the above discussions. Let's make a try:

  istream& operator>>(istream& is, Range& r)
  {
    if (!is.ipfx())
      return is;
    char c;
    is >> c;
    if (c != '[')
      // signal error somehow and roll back stream.
      ;
    int upper;
    is >> upper;
    is >> c;
    if (c != ',')
      // signal error somehow and roll back stream.
      ;
    int lower;
    is >> lower;
    is >> c;
    if (c != ']')
      // signal error and roll back stream.
      ;
    r=Range(upper,lower);
    is.isfx(); // ERROR! Does not exist!
    return is;
  }
Hmm... OK, so reading wasn't as easy... There are three issues above that needs to be resolved. How to signal error, how to roll back the stream, and how to deal with the suffix function, since the guess "is.isfx()" was wrong. Let's begin from the easy end, the suffix function. The solution is that there isn't one, so we needn't even try. The problem is fixed by removing the faulty line (don't you just love bugs that you fix solely by removing code!) Rolling back the stream is interesting indeed, since it's very difficult to do. In fact, it's almost impossible. We can put back a character. One character, that is all that is guaranteed to work. In other words, our only chance is if the first character read is not right. Putting back a character is done with "istream::putback(char)". It's also absolutely necessary that the character put back is the same as the last one read, otherwise the behaviour is undefined (which literally means all bets are off, the program may do *anything*, but in practice it means you cannot know if it just backs a position, or actually changes the character.)
The obvious solution to signalling an error, to throw an exception, is wrong. The reason is conceptual. Use exceptions to signal exceptional situations, and other means to handle the expected. The wrong input *is* expected. Remember you're dealing with input generated by human beings here. Sure you can, in theory, demand that the users of your program enter the exact right data in the exact correct format every time, but you won't be very popular among them, and soon will have none. No, erroneous user input is expected, and thus not exceptional, and thus not to be handled with exceptions. How then?
A stream object has an error state consisting of three orthogonal failure flags. "bad", "eof" and "fail". "eof" is used to signal end of file, a not too unusual situation (as a matter of fact, a situation most programs rely on, but if it occurs in the middle of reading something, it's usually a failure.) "fail" is the one we're interested in here, it's used to signal that we received something that was not what we expected, but the stream itself is OK. "bad" is something we hope to never see, since it means the stream is really out of touch with reality and we cannot trust anything from it (I've only seen this one once, and it was due to a bug in a library!) I guess we can expect "bad" if reading from a file, and hit a bad sector.
So, what we should do if we read something unexpected, is to set the stream state to "fail." This is done with the odd named member function "clear(int)". "clear" sets the status bits of the stream to the pattern of the integer parameter (which defaults to 0, so if nothing is passed, the name makes sense.) The bits we can set are "ios::badbit", "ios::failbit" and "ios::eofbit". We can get the current status bits by calling "is.rdstate()", and usually we want to do that when setting or resetting a status bit, since we want to affect only that bit, and leave the other bits as they were before the call. The status bits can also be checked with the calls "is.fail()", "is.bad()" and "is.eof()" (which return 0 if the bit they represent is not set, and non-zero otherwise.) A fourth call "is.good()" returns non-zero if no error state bits are set, and 0 otherwise. Now with the above in mind, let's make another try:

  istream& operator>>(istream& is, Range& r)
  {
    if (!is.ipfx())
      return is;
    char c;
    is >> c;
    if (c != '[')
    {
      is.putback(c);
      is.clear(ios::failbit|is.rdstate());
      return is;
    }
    int upper;
    is >> upper >> c;
    if (c != ',')
    {
      is.clear(ios::failbit|is.rdstate());
      return is;
    }
    int lower;
    is >> lower >> c;
    if (c != ']')
    {
      is.clear(ios::failbit|is.rdstate());
      return is;
    }
    if (is.good()) {
      if (upper >= lower)
        r=Range(upper,lower);
      else
        is.clear(ios::failbit|is.rdstate());
    }
    return is;
  }
This actually solves the problem as far as is possible. The call to "is.ipfx()" not only synchronizes the input stream with output streams, but also checks for error conditions and reads past leading white space. If the first character read is not a '[', we put the character back and set the fail bit (the order is important, "putback" is not guaranteed to work if the stream is in error.) After this we read the upper limit of the range, and the separator. Note that operator>> for built in types skips leading whitespace, so we needn't work on that at all. If the separator is not ",", mark the stream as failed, and return. Then read the lower limit and the terminator. If the terminator is not ']', we set the stream state to failed and return. If reading of either upper limit or lower limit failed, the stream is set to fail state, and other reads will not do anything at all (not even alter the stream error state,) thus the check near the end for "is.good()" is enough to know if all parts were read as we expected. If they were, all we need to do is to check that the upper limit indeed is at or above the lower limit (precondition for the range) and if so set "r" (since we haven't declared an assignment operator, the compiler did it for us, so the call is valid,) otherwise set the fail error state. How well do we match the 7 item wish list? You check and judge; I think we're doing fine, and in fact better than what can be found in most books on the subject.

Formatting

There are a number of ways in which the output format of the fundamental types of C++ can be altered, and a few ways in which the requirements on the input format can be altered. For example, a field width can be set, and alignment within that field. For integral types, the base can be set (decimal, octal, hexadecimal). For floating point types the format can be fixed point or scientific. All of these, and yet some, are controlled with a few formatting flags, and a little data. All flags are set or cleared with the member functions "os.setf()" and "os.unsetf()". I think they're difficult to use, but fortunately there are easier ways of achieving the same effect, and we'll visit those later.
The base for integral output is altered with a call to "os.setf(v, ios::basefield)", where v is one of "ios::hex", "ios::dec" or "ios::oct". As a small example, consider:

  #include 

  int main(void)
  {
    int i=19;
    cout << i << endl;
    cout.setf(ios::hex, ios::basefield);
    cout << i << endl;
    cout.setf(ios::oct, ios::basefield);
    cout << i << endl;
    cout.setf(ios::dec, ios::basefield);
    cout << i << endl;
    return 0;
  }
The result of running this program is:

  19
  13
  23
  19
The base is converted as expected, but there is no way to see what base it is. This can be improved with the formatting flag ios::showbase, so let's set that one too.

  int main(void)
  {
    int i=19;
    cout.setf(ios::showbase);
    cout << i << endl;
    cout.setf(ios::hex, ios::basefield);
    cout << i << endl;
    cout.setf(ios::oct, ios::basefield);
    cout << i << endl;
    cout.setf(ios::dec, ios::basefield);
    cout << i << endl;
    return 0;
  }
The output of this program is

  19
  0x13
  023
  19
That's more like it, right? The call to "setf()" for setting the "ios::showbase" flag is different, though. "setf()" is overloaded in two forms. One accepts a set of flags and a mask, the other one a full set of flags only. All the formatting flags of the iostreams are represented as bits in an integer, and the version with the mask clears the bits represented by the mask, except those explicitly set by the first parameters. Formatting bits not represented by the mask will remain unchanged. The second form, the one accepting only one parameter, sets the flags sent as parameter, and leaves the others unchanged (in other words, it bitwise "or"es the current bit-pattern with the one provided as the parameter.) Now you begin to see why this is messy. If the masked version is called, and the mask is "ios::basefield", the only formatting flags of the stream that will be affected are "ios::hex" or "ios::dec" or "ios::oct". The three of these are mutually exclusive, so a call to "os.setf(ios::hex)", is potentially dangerous (what if "ios::oct" was already set? Then you'd end up with both being set.) The second parameter "ios::basefield" guarantees that if you set "ios::hex", then "ios::oct" and "ios::dec" will be cleared. While it's possible to set two, or all three of these flags at the same time, it's not a very good idea (yields undefined behaviour.) That was setting the base for integral types, now for something that's common to all types, field width and alignment. The field width is set with "os.width(int)", and the curious can get the current field width by calling "os.width(void)." Simple enough, let's try it out:

  #include 

  int main()
  {
    cout << '[' << -55 << ']' << endl;
    cout.width(10);
    cout << '[' << -55 << ']' << endl;
    cout << '[';
    cout.width(10);
    cout << -55 << ']' << endl;
    cout << '[' << -55 << ']' << endl;
    return 0;
  }
Executing this programs shows something interesting; the width set does not affect the printing separate characters, and the width is reset after printing the first thing that uses it. This is not very intuitive I think. The result of running the program is shown below:

  [-55]
  [       -55]
  [       -55]
  [-55]
Had you expected this? I didn't, for sure. Now, let's play with alignment within a field. If the field width is not set, or the field width set is smaller than that necessary to represent the value to be printed, alignment doesn't matter, but if there's extra room, alignment does make a difference. Alignment is set with the two parameter version of "os.setf()", where the first parameter is one os "ios::left", "ios::right", or "ios::internal", and the second parameter is "ios::adjustfield". As with the base for integral types, the three alignment forms are mutually exclusive, so don't set two of them at the same time. Let's alter the width setting program to show the behaviour.

#include 

int main()
{
  cout.setf(ios::right, ios::adjustfield);
  cout << '[' << -55 << ']' << endl;
  cout.setf(ios::left, ios::adjustfield);
  cout << '[' << -55 << ']' << endl;
  cout.setf(ios::internal, ios::adjustfield);
  cout << '[' << -55 << ']' << endl;
  cout.width(10);
  cout.setf(ios::right, ios::adjustfield);
  cout << '[' << -55 << ']' << endl;
  cout.width(10);
  cout.setf(ios::left, ios::adjustfield);
  cout << '[' << -55 << ']' << endl;
  cout.width(10);
  cout.setf(ios::internal, ios::adjustfield);
  cout << '[' << -55 << ']' << endl;
  return 0;
}
The result of running this is, after the above explanations, not very surprising:

  [-55]
  [-55]
  [-55]
  [       -55]
  [-55       ]
  [-       55]
Well, OK, I found the formatting of "ios::internal" to be a bit odd, but it kind of makes sense. If the field width is larger than that required for a value, the current alignment defines where in the field the value will be, and where in the field space will be. Space, but the way, is just the default, we can change the "padding character", by calling "os.fill(char)", and get the current value with a call to "os.fill(void)". Let's exercise that one too:

  #include 

  int main()
  {
    cout.width(10);
    cout.fill('.');
    cout << -5 << endl;
    cout.width(10);
    cout << -5 << endl;
    return 0;
  }
Running it yields the surprising result

  ........-5
  ........-5
Why was this surprising? Earlier we saw that the field width is "forgotten" once used. The pad character, however, remains the same until explicitly changed. Now that you have the general idea, why not try the other formatting flags there are:
  • ios::fixed and ios::scientific control the format of floating point numbers (the mask used is ios::floatfield.)
  • ios::showpos controls whether a "+" should be prepended to positive numbers or not (just like a "-" is prepended to negative numbers.
  • ios::uppercase controls whether hexadecimal digits should be displayed with upper case letters or lower case letters.
  • ios::showpoint controls whether the decimals should be shown for floating point numbers if they are all zero.
The only thing remaining for formatting is "os.precision", which comes in two flavours. One without parameters which reports the current precision, and one with an int parameter. The unpleasant thing about this parameter, is that many compilers interpret it differently. Some think the precision is the number of digits after the decimal point, while most think it's the number of digits to display. The November 1997 draft C++ standards document (which, by the way, most probably is the final C++ standards document,) says the number of digits after the decimal point is what's controlled, but I'm not sure if that's what the current standards document says. At any rate, inconsistencies aside, this is a mess, isn't it?

An easier way

The authors of the I/O package realized that this is a mess, so they defined something called "manipulators." You've already used one manipulator a lot, "endl." A manipulator does may, or may not, print something on the stream, but it will alter the stream in some way. For example "endl" prints a new line character, and flushes the stream buffer. There are two kinds of manipulators, those accepting a parameter, and those that does not. Let's first focus on those that don't, just like "endl." The ones available are: "dec", "hex", "oct", "endl", "ends", and "flush". Their use is simple:

  #include 

  int main(void)
  {
    cout << hex << 127 << " " << oct << 127 << " "
        << oct << 127 << endl;
    return 0;
  }
The advantage of this is both that the code becomes clearer, and that there's no way you can accidentally set illegal base flag combinations. "ends" is rarely used, it's there to print a terminating '\0' (the terminating '\0' of strings is never printed normally.) "flush" flushes the stream buffer (i.e. forces printing right away.) How do these manipulators work? There's a rather odd looking operator<< for output streams. It looks like:

  ostream& operator<<(ostream& (*f)(ostream&))
  {
    return f(*this);
  }
Now, what on earth does this mean? It means that if you have a function accepting an ostream& parameter, and returning an ostream&, that function can be "printed," and if you do, the function will be called with the stream as its parameter. Let's exercise this by rolling our own "left" alignment manipulator:

  ostream& left(ostream& os)
  {
    os.setf(ios::left, ios::adjustfield);
    return os;
  }
This function matches the required signature, so if we "print" it with "cout lt;< left", the above mentioned operator<< is called, and it in its turn calls the function for the stream, so "cout << left", actually ends up as "left(cout)". Cool, eh? Roll your own "right" and "internal" manipulators as a simple exercise (they're handy too.) Then there are some manipulators accepting a parameter. To access them, you need to #include . The ones usually accessed from there are "setw" (for setting the field width,) "setprecision", and "setfill". Their use is fairly straightforward and doesn't require any example.
Every compiler I've seen provides its own mechanism for writing such manipulators, so doing it in a portable way is very difficult. Or actually, it isn't if you skip the mechanism offered by your compiler vendor and do the job yourself, because it really is simple. Let's write one that prints a defined number of spaces:

  class spaces
  {
  public:
    spaces(int s) : nr(s) {};
    ostream& printOn(ostream& os) const {
      for (int i=0; i < nr; ++i)
        cout << ' ';
      return os;
    }
  private:
    int nr;
  };

  ostream& operator<<(ostream& os, const spaces& s)
  {
    return s.printOn(os);
  }
Can you see what happens if we call "cout << spaces(40)"? First the object of class "spaces" is created, with a parameter of 40. That parameter is in the constructor stored in the member variable "nr". Then the global operator<< for an ostream& and a const space& is called, and that function in its turn calls the printOn member function for the spaces object, which goes through the loop printing space characters. I think writing manipulators requiring parameters this way is lots easier than trying to understand the non-portable way provided by your compiler vendor.
Now something for you to think about until next month, what about our I/O of our own classes with respect to the formatting state of the stream? How's the "Range" class printed if the field width and alignment is set to something? How should it be printed (hint, your probably want it printed differently from what will be the case if you don't take care of it.)

Exercises

  • Find out which formatting parameters "stick" (like the choice of padding character) and which ones are dropped immediately after first use (like the field width.)
  • With the above in mind, and remembering that destructors can be put to good work, write a class which will accept an ostream as its constructor parameter, and which on destruction will restore the ostreams formatting state to what it was on construction.
  • Experiment with the formatting flags on input, which have effect, and which don't? Of those that do have an effect, do they have the effect you expect?
  • Write an input manipulator accepting a character, which when called compares it with a character read from the stream, and sets the ios::fail status bit if they differ.

Recap

This month you've learned a number of things regarding the fundamentals of C++ I/O. For example
  • How to set, clear and recognise the error state of a stream.
  • Why exceptions are not to be used when input is wrong.
  • How to make sure your own classes can be written and read.
  • The very messy, and the somewhat less messy way of altering the formatting state of a stream.
  • How to write your own stream manipulators.

Standards update

  • The prefix and postfix functions are history. Instead you create an object of type istream::sentry or ostream::sentry, and check it, like this:
    
          istream& work(istream& is)
          {
            istream::sentry cerberos(is);
            if (kerberos) {
              ...
            }
          return is;
          }
        
    The destructor of the sentry object does the work corresponding to that of the postfix function.
  • "istream" and "ostream" are in fact not classes in the standard, but typedef's for class templates. The class templates are template class basic_istream, and template class basic_ostream. "istream" is typedefed as "basic_istream >", and "ostream" as "basic_ostream >". There's also the pair "wistream" and "wostream", that are streams of wide characters.
  • The mechanism for writing manipulators is standardised (and heavily based on templates.) I still think it's easier to write a class the way I showed you.
  • Any operation that sets an error status bit may throw an exception. Which error status bits cause exceptions to be thrown is controlled with an exception mask (a bit mask.) By default, though, no exceptions are thrown.
  • Formatting of numeric types (and time) is localised. By default most implementations will probably use the same formatting as they do today, but with the support for "imbuing" streams with other locales (formatting rules.)
  • The header name is (no .h) and the names actually std::istream and std::ostream (everything in the C++ standard library is named std::whatever, and every standard header is named without trailing .h)

An Introduction to C++ Programming - Part 4

Why templates

The last two articles made some effort in perfecting a stack of integers. Today, I want a stack of doubles. What do I do? Rewrite it all and call it doublestack? It's one alternative. Then I want a stack of char* (another rewrite) and a stack of bicycles (yet a rewrite, and a bizarre view). And then, of course, we end up with 4 versions of stack, all with identical code, just one data member in the internal struct with different type for all of them. Sigh...
There's of course the C kind of solution, always make it a stack of void*, and cast to whatever you want (and just hope you won't cast to the wrong type). No, the latter alternative isn't an alternative in my book. Type safety is essential for writing solid programs (Smalltalk programmers disagree). The former alternative isn't an alternative either. Just think of this little nightmare, 4, at least, more or less identical pieces of code. When you find a bug (when, not if), you have to correct it in as many places. Yuck... OK, so I guess I've explained what templates are for. They're the solution to the above problem. But how?

Function templates

In the first C++ article, I wrote a set of overloaded functions called "print", which printed parameters of different types. The code in each of those functions was exactly the same (exactly the kind of redundancy that's always to be avoided). This is an ideal place for a template. Here's what a template function for printing something can look like:

  template 
  void print(const T& t)
  {
    cout << "t=" << t << endl;
  }
The keyword "template" says we're dealing with a template. When declaring/defining a template, there's always a template parameter list, enclosed in a '<', '>' pair. The template parameter for this template is "class T". This means that the template deals with a type, some type, called T. Despite the keyword "class", T does not have to be a class, it can be any of the built in types, enumerations, structs, and so on (if you have a modern compiler, it will accept the keyword "typename" instead of "class", although "class" will still work). The name "T" is of course arbitrarily chosen. It could be any name. After this comes the function definition, where T is used just as if it was a legal type.
For writing a template function, that's really all there is. Here are some examples using it:

  int main(void)
  {
    print(5); // print
    print(3.141592); // print
    print("cool"); // print
    print(2); // print again.
    return 0;
  }
Weird? OK, time for some demystifying. The code for the template, is not a function. It's a function template, something which the compiler uses to create functions. This is very much like a cookie cutter. Once you have a cookie cutter, you can make cookies with the shape of the cutter. More or less any kind of cookie can be made with that cutter. When the compiler reads the function template, it does pretty much nothing at all, other than to remember that there's a function template with one template parameter and the name "print". When the compiler reaches "main", and sees the call to "print(5)", it looks for a function "print" taking an "int" as a parameter. There is none, so it expands the template function "print" with the type "int", and actually makes a new function. Note that this is done by the compiler at compile time. The same happens for the other types. The compiler always first checks if there is a function available, and if there isn't, it tries to create it by expanding the function template. This order of things is necessary to avoid unnecessary duplication. After all, "print(2)" uses the same function as "print(5)" does, rather than creating yet another copy of it. Let's compile and run:

  [c:\desktop]icc /Q temp_test.cpp

  [c:\desktop]temp_test.exe
  t=5
  t=3.14159
  t=cool
  t=2

  [c:\desktop]
Although it does not seem like it, type safety is by no means compromised here. It's just seen in a somewhat different way. For the function template "print", there's only one requirement on the type T; it must be possible to print it with the "<<" operator to "cout". If the type cannot be printed, a compilation error occurs. To test it, here's what GCC says when trying to print the "intstack" from last month:

  [c:\desktop]gcc temp_test2.cpp -fhandle-exceptions -lstdcpp
  temp_test2.cpp: In function `void print(const class intstack &)':
  temp_test2.cpp:285: no match for `operator <<(class ostream, class
  intstack)'
GCC delivered a compilation error, since the type "intstack" cannot be printed with "<<" on "cout" (the error message says "ostream", which is correct. We'll deal with that later this fall/early winter, in one or a few articles on C++ I/O). Here the compiler generated a new function, called a template function, where every occurrence of "T" (only one, in the function parameter list) is replaced with "intstack". After having generated the function, it compiled it, and noticed the error. Note that a function is not generated from a function template until a call is seen (the compiler cannot know what types to generate the function for before that).
Please note the different meanings of the terms "function template", and "template function". The "function template" is what you write. It's a template from which the compiler generates functions. The compiler generated functions are the template functions. The "function template" is the cookie cutter, while the "template function" is the cookie. One example of a template function above, is "print()" (i.e. the "int" version of print).

Templates and exceptions

As you may have noticed, I didn't write an exception specifier for the "print" function template. This was not a mistake, nor was it sloppiness. The drawback with templates is that they make writing exception specifiers a bit difficult. I could try to make the promise that the function "print" does not throw exceptions, by adding the exception specifier "throw ()", but that would not be wise. The problem is that I cannot know what kind of type T will be, and I cannot know if operator<< on that type can throw an exception or not. What if it does? If so, and the function template had an empty exception specifier list, "unexpected" would be called, and the program terminate. Not nice. This problem is something I strongly dislike about C++, but this is how it works, and there's not much to do about it. I wish there was a way to say "The exceptions that might be thrown are the ones from operator<< on T" but there is no way to say that, other than as a comment. Note that not writing an exception specifier means that any exception may be thrown.

Class templates

Just as you can write functions that are independent of type (and yet type safe!) we can write classes that are independent of type. In a sense, class templates exist as builtins in C and C++. You have arrays and pointers (and references) that all act on a type, some type. The type they act on does not change their behaviour, they're still arrays, pointers and references, but of different types. Let's explore writing a simple class template, by improving the old "Range" class from lesson 2. In case you don't remember, the original "Range" looks like this:

  struct BoundsError {};
  class Range
  {
  public:
    Range(int upper_bound = 0, int lower_bound = 0)
     throw (BoundsError);
     // Precondition: upper_bound >= lower_bound
     // Postconditions:
     //   lower == upper_bound
     //   upper == upper_bound

    int lowerBound() throw ();
    int upperBound() throw ();
    int includes(int aValue) throw ();
  private:
    int lower;
    int upper;
  };
This class is a range of int. There's no reason, however, why it shouldn't be a range of any type. Writing a class template is in many ways similar to writing a function template:

  struct BoundsError {};

  template 
  class Range
  {
  public:
    Range(const T& upper_bound = 0,
    const T& lower_bound = 0);
     // Precondition: upper_bound >= lower_bound
     // Postconditions:
     //   lower == upper_bound
     //   upper == upper_bound
     // Throws:
     //   Bounds error on precondition violation
     //   Whatever T's copy constructor throws.
     //   Whatever operator < on T throws.

    const T& lowerBound() throw ();
    const T& upperBound() throw ();
    int includes(const T& aValue);
      // Throws: Whatever operator>= and operator <= on T
      // throws.
  private:
    T lower;
    T upper;
  };
As can be seen, after "template ", on line 3, T is used just as if it was a type existing in the language. I've changed the constructor so that it accepts the parameters as const reference instead of by value. The reason is performance if T is a large type (if passed by value, the parameters must be copied and the copying may be an expensive operation). I've also removed the exception specifier, and instead used a comment, since after all, there is no way to know if T throws anything. "lowerBound", "upperBound" and "includes" uses const T& instead of value, for the same reason as the constructor does. "lowerBound" and "upperBound" can safely have empty exception specifiers, since those member functions do not do anything with the T's. They just return a reference to one of them. "includes" on the other hand, does need the unfortunate comment.
Time for the implementation, which will include some news:

  template 
  Range::Range(const T& upper_bound,
                  const T& lower_bound)
    : lower(lower_bound), // copy constructor
      upper(upper_bound)  // copy constructor
  {
    if (upper < lower) throw BoundsError();
  }

  template 
  const T& Range::lowerBound() throw ()
  {
    return lower;
  }

  template 
  const T& Range::upperBound() throw ()
  {
    return upper;
  }

  template 
  int Range::includes(const T& aValue)
  {
    return aValue >= lower && aValue <= upper;
  }
The syntax for member functions is very much the same as that for function templates. The only difference is that we must refer to the class (of course), and we must specify that it's the template version of the class, by adding "" after the class name. The reason is that we're not dealing with a complete class, but with a class template, and we must be explicit about that. We must also precede every member function with "template ". There isn't much more to say about this. Let's have a look at how it's used:

  #include 

  int main(void)
  {
    Range ri(100,10);
    Range rd(3.141592,-3.141592);
    if (ri.includes(55))
    {
      cout << "[" << ri.lowerBound() << ", "
     << ri.upperBound() << "] includes 55"
     << endl;
    }
    if (!rd.includes(62))
    {
      cout << "[" << rd.lowerBound() << ", "
     << rd.upperBound()
     << "] does not include 62" << endl;
    }
    return 0;
  }
Take a careful look at the syntax here. To use a class template, you must explicitly state what type it is for. There is unfortunately no way to say "Range(5,10)" and have the compiler automatically understand that you mean "Range(5,10)". As with function templates, a class template is expanded when it's referred to, so when the compiler first sees "Range", it creates the class, by expanding whatever is needed. The compiler will also treat every member function just as any template function, i.e. the code will not be expanded until it is called from somewhere. The above code calls all members of "Range", but had "includes" not been called, it would not have been expanded. One unfortunate side of this is that "includes" could actually contain errors, and this would be unnoticed by the compiler, until "includes" was called.

Advanced Templates

Now that the basics are covered, we should have a look at some power usage (this section is abstract, so it may require a number of readings).
One unusually clever place for templates, is as something called "traits classes". A traits class is never instantiated, and doesn't contain any data. It just tells things about other classes, that is its sole purpose. The name "traits class" is odd. Originally they were called "baggage classes", since they're useless on their own, and belong to something else, but for some reason some people didn't like the name, so it was changed. My intention is to write a traits class, which tells the name of the type it is specialized for (explanation of that comes below), and to write a special template print, which prints ranges looking like the constructor call for the range, and finally, a function template, which is used to create ranges, without needing to specify the type. When done, I will be able to write:

  print(make_range(10,4));
and when executed, see:

  Range(10,4)
Magic? No, just templates! Here we go...
A traits class, is a simple class template. The traits class needed here, is one that tells the name of a type. The class template just looks like this:

  template 
  class type_name
  {
  public:
    static const char* as_string();
  };
That is, it holds no data, and the member function is declared as "static". This is the way traits classes usually look. No data, and only static member functions. A member function declared static, is different from normal member functions, in that it does not belong to an instance, but belongs to the class itself. Here's an example:

  class A
  {
  private:
    int data;
  public:
    void f(void);
    static void g(void);
    static void h(void);
  };

  void A::f(void)
  {
    cout << data << endl;
  }

  void A::g(void)
  {
    cout << data << endl; // error! Cannot access data.
  }

  void A::h(void)
  {
    cout << "A::h" << endl;
  }

  int main(void)
  {
    A a;
    a.f(); // prints something.
    a.h(); // prints "A::h"
    A::h(); // also prints "A::h"
    A::f(); // Error, f is bound to an object, and must be
            // called on an object.

    return 0;
  }
"A::g()" is in error, because it's declared static, and thus not bound to any object, and as such cannot access any member data, since member data belongs to objects. The calls "a.h()" and "A::h()" are synonymous. Since "h" is not tied to an object, it can be called through the class scope operator "A::", which means it's the "h" belonging to the class named "A".
Calling "A::f()" is an error, since it is not static. This means it belongs to an object, and must be called on an object (through the "." operator).
Now back to traits classes. The whole idea for traits classes is one of "specialization". The class template is the general way of doing things, but if you want the class to take some special care for a certain type, you can do what's called a specialization. A member function specialization is usually not declared, just defined, like this:

  const char* type_name::as_string()
  {
    return "char";
  }
Of course, if you have a top modern compiler, you'll get a compilation error. The syntax has changed, so compilers very much up to date with the standardization requires you to write like this:

  template <>
  const char* type_name::as_string()
  {
    return "char";
  }
A minor, but in a sense, understandable difference. The "template <>" part clarifies that it's a template we're dealing with, but the template parameter list is empty, since we're specializing for known types. This is how traits classes usually look. They have a template interface, the class, which declares a number of static member functions. Those member functions are intended to tell something about some other class. Normally, the template member functions are not defined, instead specializations are. Their purpose is only to tell something about other classes, nothing else.
Now, we can use the "type_name" traits class for "char" as follows:

  cout << type_name::as_string() << endl;
If we try for a type we haven't specialized, such as "double", we'll get an error when compiling. You can of course make any specializations you like. Please add all the fundamental types. Now over to the print template, which with the above seems fairly simple. It's supposed to accept an instance of a "Range", and print it, just as the constructor call for the "Range" was done. Piece of cake:

  template 
  void print(const Range& r)
  {
    cout << "Range<" << type_name::as_string()
         << ">(" << r.upperBound() << ", "
         << r.lowerBound() << ")" << endl;
  }
Here we see two new things; the parameter for the function template need not be the template parameter itself. It needs to be something that makes use of the template parameter, though (for all except the absolutely newest compilers, all template parameters must be used in the parameter list for the function). The other new thing is how elegantly the "type_name" traits class blends with the function template. For being such an incredibly simple construct, the traits classes are unbelievably useful. Note also that this means we cannot print ranges of types for which the "type_name" traits class is not specialized. Now we're almost there. We can now write:

  print(Range(10,5));
And it will work (if we specialize "type_name::as_string()", that is). Now for the last detail; the function template that creates "Range" instances.

  template 
  Range create_range(const T& t1, const T& t2)
  {
    return Range(t1,t2);
  }
Doesn't seem too tricky, now does it? There actually is no catch in this. The function template is by the compiler translated to a template function, using the types of the parameters. If the types differ in a call, the compiler will give you an error message. When the type is known, it will know what kind of "Range" to create and return. We can now write:

  print(make_range(10,5));
just as we planned to. Neat, eh? If you want to learn more about traits classes, have a look at Nathan Meyers traits article from the June '95 issue of C++ Report.

Exercises

  • Biggie: Rewrite last months "intstack" as a class template, "stack" What happens if the copy constructor, operator== or destructor of T throws exceptions?
  • When can you, and when can you not use exception specifiers for templates?
  • What are the requirements on the type parameter of the templatized "Range"? Can you use a range of "intstack"?
  • What are the requirements on the type parameter of the templatized "stack"?

Recap

Quite a lot of news this month. You've learned:
  • how to write type independent functions with templates, without sacrificing type safety.
  • how the compiler generates the template functions from your function template.
  • about template classes, which can contain data of a type not known at the time of writing.
  • that templates restricts the usefulness of exception specifiers.
  • how to specialize class templates for known types.
  • how to write and use traits classes.

An Introduction to C++ Programming - Part 3

References

C++ introduces an indirect kind of type that C does not have, the reference. A reference is in itself not a type, it always is a reference of some type, just like arrays are arrays of something and pointers are pointers to something.
A reference is a means of indirection. It's a way of reaching another variable. This may sound a lot like a pointer, but don't confuse the two, they're very different. See a reference more as an alias for another variable. Some details about references:
  • References must be initialized to refer to something. There is no such thing as a 0 reference.
  • Once bound to a variable there is no way to make the reference refer to something else.
  • There is no such thing as "reference arithmetic."
  • You cannot get the address of a reference. You can try to, but what you get is the address of the variable referred to.
References are denoted with an unary "&", just the same ways as pointers are denoted with an unary "*". Let's have a look at an example:

  int main(void)
  {
    int i = 0;
    int& x;     // error, would be an unbound reference.
    int& r = i; // r now refers to i.
    ++r;        // now i == 1, r still refers to i.
    if (&r != &i)
      throw "Broken compiler";
    return 0;
  }
From this, one may wonder, what on earth are references for? Why would anyone want them? Well, for one, they offer a certain security over pointers; it's so easy to get a pointer referring to something that doesn't exist, or something else than the intended. They're also handy as a short-cut into long nested structs" and arrays. Here's an example:

  struct A {
    int b[5];
    int x;
    char d;
  };

  struct C {
    A* p[10];
    int q;
  };

  C* pc;

  // and somewhere else, pc is given a value and is
  // here used.

  A& ra = pc->p[2];
  ra.b[3]=5; // pc->p[2]->b[3] = 5;
  ra.b[4]=2; // pc->p[2]->b[4] = 2;
The reference in this case just makes life easier. In parts 1 and 2, I used references when catching exceptions. References are also often used for parameters to functions. If we look at the exceptions, it means that instead of getting a local copy of the thing thrown, we get a reference to the thing thrown, and we can manipulate it if we want to, instead of manipulating our own copy. The same goes for parameters to functions. Passing an object by reference instead of by value, can some times be necessary, and sometimes beneficial in terms of performance. The reason for the performance benefit is that the when passing a parameter by value, the object is copied; the function uses a local object of its own, that is identical to the one you passed. If copying the object is an expensive operation, which in some cases it is, then passing by reference is cheaper. However, passing parameters by reference can be dangerous. When you do, the function has access to the very object you pass, and if the function modifies it, the caller better be prepared for that. A commonly used way around this is to declare the parameter as a "const" reference. This means that you get a reference, as before, but the reference is treated as was it a constant, and because of this all attempts to change its value will cause a compile time error.
Here's an example of passing a parameter by "const" reference. It uses the "intstack" from the previous lesson:

  // put the declaration of intstack here...
  void workWithStack(const intstack& is)
  {
    // work with is
    is.pop(); // Error, attempting to alter
              // const reference.
  }

  int main(void)
  {
    intstack i;
    i.push(5);
    workWithStack(i);
    return 0;
  }
Since the "intstack" class does not have a copy constructor (it was declared private, remember?) it is impossible to pass instances of it to functions in other ways than by reference (or by pointer.) There are situations when a reference is dangerous, though. One such trap, that I think all C++ programmers fall into at least once, is returning a reference to a local variable. Have a look at this:

  #include 

  int& ir(void) // function returning reference to int
  {
    int i = 5;
    return i;
  }

  int main(void)
  {
     cout << ir() << endl;
     return 0;
  }
What will this program print? It's hard to tell. It could be anything, if it prints at all. It might just crash. Why? The function "ir" returns a reference to an "int", that the "main" function prints. So far so good, but what does the reference returned refer to? It refers to the local variable "i" in "ir". If you remember the "tracer" examples from the previous lesson, you remember that the variable ceases to exist when exiting the function. In other words, the reference returned refers to a variable that no longer exists! Don't do this! Or rather, do it now, at once, just to have your one time mistake over with :-)

What's a class?

Now for the theoretical biggie. What, exactly, is the meaning of a class. When should you write a class, what should the class allow you to do, and what's a good name for a class? What's the relation between classes and objects?
When you write programs in Object Oriented Programming Languages, be it C++, Objective-C, SmallTalk, Eiffel, Modula-3, Java or whatever, you write classes. A class is, as I mentioned in part 2, a method of encapsulation, but more importantly, a class is a type. When you define a class, you add a new type to the language. C++ comes with a set of built in types like "int", "unsigned" and "double". In the previous lesson, when we wrote the class "intstack", we introduced a new type to the language, which programs could use, the stack of integers. The member functions of the class, describe the semantics of the type. With the built in integral types, we have operations like adding two instances of the type, yielding a third instance, which value happens to be the sum of the values of the other two. We can increment the value of instances of the type with operations like ++, and so on. With the "intstack", we had the operations "pop", "top", "push" and "nrOfElements", in addition to well defined construction and destruction of instances.
So, how can you know what classes to make? Classes are, as a rule of thumb, descriptions of ideas. "Bicycle" for example, is a classic example of a class. The idea "Bicycle" that is, not my particular bicycle. My bicycle is a physical entity that is currently getting wet in the rain. The idea of bicycle is a very good candidate for a class. What my bicycle is, on the other hand, is a good candidate for an instance of the class "Bicycle." So, when thinking of the problem you want to solve, you might have a good candidate for a class X, if you can say "The X ...", "An X...", or "A kind of X...". The objects are the instances of types (yes, an instance of type "float" is also an object, they need not be instances of classes.) A class represent the idea, and the functions that represent the semantics. Usually instances of the class has a state (for example, the state of a stack is the elements in it, and their order.) Having state means that the same member function can give different results depending on what has been done to the object before calling the member function (again, with a stack, the value returned by "top()" or "nrOrElements()" depends on the history of "push()" and "pop()" calls.) The class has member data to represent state. There are, however, exceptions to this rule of thumb. For example, is a mathematical function a class that you'd like to have instances of to toy with in your program? According to the rule, it is not, since a mathematical function is state less. In most situations, the answer would, as expected, be no, but if you design a program for use by electronics engineers when designing their gadgets, you better have amplifiers (multiplication,) adders, subtractors and so on, or they won't use your program.
Note that objects don't exist when you write your program. Objects are run-time entities. When you write your program, what exists are types, descriptions of how instances of types can be used, and descriptions of semantics and state representation. When your program executes, the identifiers, (like "pc" in the reference example above) are replaced by bit-patterns representing objects.
So, then, what member functions should a class have? This is even harder to say, because there are so many ways to solve every problem. However, the things that you need to do, when solving your problem, to instances of types, like "Bicycle" or "intstack", must in one way or the other be expressible through the classes. If I need to ride my bicycle, it can be that the class "Bicycle", should have the member function "beRiddenBy" accepting an instance of class "Human", but it might also be that class "Human" should have the member function "ride" accepting an instance of class "Bicycle" as its parameter. If the starting point or destination are important, they probably should be parameters to the member functions. If the road itself is important, you probably need a class "Road", which you want to pass an instance of to the member function of either "Bicycle::beRiddenBy" or "Human::ride".
Given this, you might start to feel like someone's been fooling you. This Object Oriented Programming thing is a hoax! What it's all about, is class oriented programming. The objects are, after all, just the run time instances of the classes.

The Orthodox Canonical Form

The basic operations you should, in general, be able to do with objects of any class is construction from scratch, construction by copying another instance, assignment and destruction. This places a slightly heavier burden on us, compared to the work with the "intstack." The "intstack" guaranteed that no matter what happened, an instance was always destructible. The Orthodox Canonical Form poses the additional requirement that an instance must always be copyable. Normally this extra burden is light, but there are tricky cases. Construction from scratch we've seen. Construction by copying is done by the copy constructor.
Given a class named C, the copy constructor looks like this:

  class C
  {
  public:
    C(const C& c); // copy constructor
    // other necessary member functions.
  };
The job of the copy constructor is to create an object that is identical to another object. It is your job to make sure it does this. This does not mean that every member variable of the newly constructed object must have values identical to the ones in the original. On the contrary, they often differ. What's important, though, is that they're semantically identical (i.e. given the same input to member functions, they give the same response.) The "intstack" for example must make its own copy of the stack representation in the copy constructor. This means that the base pointer will differ, but as far as you can see through the "push", "pop" and "top" member functions, there is no difference between the copy and the original. Next in line is copy assignment. Again, given a class C, the copy assignment operator looks like this:

  class C
  {
  public:
    C& operator=(const C&);
    // other necessary member functions.
  };
Writing the copy assignment operator is more difficult than writing the copy constructor. Not only does the copy assignment operator need to make the object equal to its parameter, it also needs to cleanly get rid of whatever resources it might have held when being called (The copy constructor does not have this problem since it creates a new object that cannot have held any data since before.) The return value of an assignment operator is (by tradition, not by necessity) a reference to the object just assigned to. When inside a member function (the assignment operator as defined above is a member function) the object can be reached through a pointer named "this," which is a pointer to the class type. For the class C, above, the type of "this" is "C* const" This means that is's a pointer to type C, and the pointer itself is a constant (i.e. you cannot make "this" point to anything else than the current instance.) The reference to the object is obtained by dereferencing the "this" pointer, so the last statement of an assignment operator is almost always "return *this;" The difficulty of writing a good copy constructor and copy assignment operator is best shown through a classical error:

  class bad
  {
  public:
    bad(void);                  // default constructor
    bad(const bad&);            // copy constructor
    ~bad(void);                 // destructor
    bad& operator=(const bad&); // copy assignment
  private:
    int* pi;
  };

  bad::bad(void)
   : pi(new int(5)) // allocate new int on heap and
                    // give it the value 5.
  {
  }

  bad::bad(const bad& b)
   : pi(b.pi) // initialize pi with the value of b's pi
  {           // This is very bad, as you will soon see
  }

  bad::~bad(void)
  {
    delete pi;
  }

  bad& bad::operator=(const bad& b)
  {
    pi = b.pi;    // This seamingly logical and simple
    return *this; // assignment is also disasterous.
  }

  int main(void)
  {
    bad b1;
    {
      bad b2(b1); // b2.pi is now the same as b1.pi.
    } // Here b2 is destroyed, and b2's destructor is
      // called. This means that the memory area
      // pointed to by b2.pi (and hence also b1.pi) is
      // no longer valid

    bad b3(b1); // Make b3.pi point to the same invalid
                // memory area!
    bad b4;
    bad b5;
    b5 = b4; // The memory allocated by b5 was never
             // deallocated. We have a memory leak!
    return 0;
  } // The destrctor of b1 and b3 attempt to deallocate
    // the memory already dealloceted by the destructor
    // of b2. The destructors of b4 and b5 both attempt
    // to deallocate the same memory (b5 first,
    // correctly so, and then b4, which deallocates
    // already deallocated memory.
OK, so from the example it is pretty clear that it's more work than this. The copy constructor should allocate its own memory, and initialise that memory with the same value as that pointed to by the original. This goes for the copy assignment operator too, but it also needs to discard the pointer it already had. By doing so, we guarantee that the pointers owned by the objects are truly theirs, and their destructor can safely deallocate them. We do, however, have yet a problem to deal with, that of self assignment. A version of the program fixing the above issues can show you what is meant by that:

  // class declaration, default constructor and
  // destructor are identical and because of that not
  // shown here.

  bad::bad(const bad& b)
   : pi(new int(*b.pi)) // initialize pi as a new int
                        // with the value of b's pi
  { // This guarantees that both the new object and the
  } // original are destructible.

  bad& bad::operator=(const bad& b)
  {
    delete pi;            // No more memory leak
    pi = new int(*b.pi);  // Get a new pointer and
                          // initialise just as in
    return *this;         // the copy constructor.
  } // Can you spot the problem with this one?

  int main(void)
  {
    bad b1;
    {
      bad b2(b1); // b2.pi now points to its own area.
    } // Here b2 is destroyed, and b2's destructor is
      // called. This means that the memory area
      // pointed to by b2.pi is no longer valid
      // b1.pi is still valid, though.

    bad b3; // Allocate new memory
    b3=b1;  // Deallocate, and allocate new again

    b3=b3; // Whoa!! b3.pi is first deallocated, then
           // b3.pi is allocated again and initialised
           // with the value no longer available!!!
    return 0; // all OK, all objects refer to their own
              // memory, so deallocation is not a
              // problem.
  }
OK, so assigning an object to itself is perhaps not the most frequently done operation in a program, but that doesn't mean it's allowed to crash, right? So, how can we make the copy assignment operator safe from self assignment? Here are two alternatives:

  bad& bad::operator=(const bad& b)
  {
    if (pi != b.pi)
    {
      delete pi;
      pi = new int(*b.pi);
    }
    return *this;
  }

  bad& bad::operator=(const bad& b)
  {
    if (this != &b)
    {
      delete pi;
      pi = new int(*b.pi);
    }
    return *this;
  }
Common to both is that they check if the right hand side (parameter b) is the same object. If it is, the assignment is simply not done. The first alternative does this by comparing the "pi" pointer. The second by comparing the pointer to the objects themselves. The latter perhaps feels a bit harder to understand, but it's actually the one most frequently seen, because normally classes have more than one member variable to check for. Note that if your class only has member variables of types for which copying the values does not lead to problems, the tests above are not necessary. With these changes done, the class deserves a name change. It is no longer bad.
In the previous lesson, the copy constructor and copy assignment operator was declared private, to prevent copying and assignment. The reason is that a C++ compiler automatically generates a copy constructor and copy assignment operator for you if you don't declare them. The auto-generated copy constructor and assignment operator, however, will just copy/assign the member variables, one by one. In some cases this is perfectly OK. The "Range" class from the previous lesson, for example, does fine with this auto-generated copy constructor and copy assignment operator. The "intstack" on the other hand does not, since then both the original and the copy would share the same representation (and have exactly the same problem as described in the above "bad" example!)
If you decide that for your class, the auto generated copy constructor and/or copy assignment operator is OK, leave a comment in the class declaration saying so, so that readers of the source code know what you're thinking. Otherwise they might easily think you've simply forgotten to write them.
One last thing before wrapping up...

Const Correctness

When talking about passing parameters to functions by reference, I mentioned the const reference as a way to ensure that the parameter won't get modified, since the const reference treats whatever it refers to as a constant and thus won't allow you to do things that would modify it. The question is, how does the compiler know if something you do to an object will modify it? Does "pop" modify the "intstack?" Yes, it does. It removes the top element. Does "top" modify the stack? No. So, it should be allowed to call "top" for a constant stack, right? The problem is that the compiler doesn't know which member functions modify the objects, and which don't (and assumes they do, just to be on the safe side) unless you tell it differently. Since, by default, a member function is assumed to alter the object, you are, by default, not allowed to do anything at all to a constant object. This is of course hard. Fortunately we can tell the compiler differently. We can change "top" to be declared as follows:

  class intstack {
  public:
    // misc member functions
    int top(void) const throw(stack_underflow,pc_error);
    // misc other member functions
  };
It's the word "const" after the parameter list that tells the compiler that this member function will not modify the object and can safely be called for constant objects. As a matter of fact, now when we know about references, we can do even better by writing two member functions "top", one "const" and one not, with the non-const version returning a non-const reference to the element instead. This has a tremendous advantage: For constant stack objects, we can get the value of the top element, for non-constant stack objects, we can alter the value of the top element by writing like this:

  intstack is;
  is.push(5);   // top is now 5;
  is.top() = 3; // change value of top element!
There is no magic involved in this. Just as I mentioned in part one, functions can be overloaded if their parameter list differs. Member functions can be overloaded on "constness." The "const" member function is called for constant objects (or, const references or pointers, since they both treat the object referred to as if it was a constant.) The non-const member function is called for non-constant objects. Note that it is only member functions you can do this "const" overloading on. You cannot declare non-member functions "const." Our overloaded "top" member functions can be declared like this:

  class intstack {
  public:
    // misc member functions
    int top(void) const throw(stack_underflow,pc_error);
    int& top(void) throw (stach_underflow,pc_error);
    // misc other member functions
  };
This is getting too much without concrete examples. Here's a version of "intstack" with copy constructor, copy assignment operator, const version of "top" and "nrOfElements", and a non-const version of "top" (just as above.) Only the new and changed functions are included here. You'll find a zip file with the complete sources at the top.

  class intstack
  {
  public:
     // the previous memberfunctions

    intstack(const intstack& is) throw (bad_alloc);
      // Preconditions: -

    intstack& operator=(const intstack& is)
      throw (bad_alloc);
      // Preconditions: -

    unsigned nrOfElements() const throw (pc_error);
      // Preconditions: -
      // Postconditions:
      //   nrOfElements() == 0 || top() == old top()

    int& top(void) throw (stack_underflow, pc_error);
      // Preconditions:
      //   nrOfElements() > 0
      // Postconditions:
      //   nrOfElements() ==  old nrOfElements()
      // Behaviour on exception:
      //   Stack unchanged.

    int top(void) const throw(stack_underflow,pc_error);
      // Preconditions:
      //   nrOfElements() > 0
      // Postconditions:
      //   nrOfElements() ==  old nrOfElements()
      // Behaviour on exception:
      //   Stack unchanged.

  private:
    // helper functions for copy constructor, copy
    // assignment and destructor.

    stack_element* copy(void) const throw (bad_alloc);
    void destroyAll(void) throw();
  };
Since copying elements of a stack is the same when doing copy assignment and copy construction, I have a private helper function that does the job. This is not necessary by any means, but it means I won't have identical code in two places, and that is usually desirable. After all, if ever you need to change the code, you can bet you'll forget to update one of them otherwise, and you have a subtle bug that may be hard to find. With only one place to update, that mistake is hard to make. The same goes for deallocation of the stack. It is needed both in copy assignment and destructor. Since these helper functions "copy" and "destroyAll" are purely intended as an aid when implementing copy assignment, copy constructor and destructor, they're declared private. Just as a private member variable can only be accessed from the member functions of a class, and not by anyone else, member functions declared private can only be accessed from member functions of the same class. They have nothing what so ever to do with how the stack works, just how it's implemented. Here comes the new implementation of "nrOfElements." Can you see what's different from the previous lesson?

  unsigned
  intstack::nrOfElements() const throw (pc_error)
  {
    // Preconditions: -
    return elements;
    // Postconditions:
    //   nrOfElements() == 0 || top() == old top()
    // no need to check anything with this
    // implementation as it's trivially
    // obvious that nothing will change.
  }
There isn't anything at all that differs from the previous version of "nrOfElements", other than that it's declared to be "const." Had we, in this member function (or any other member function declared as "const" attempted to modify any member variable, the compiler would give an error, saying that we're attempting to break our promise not to modify the object. "const" methods are thus good also as a way of preventing you from making mistakes. Note that declaring a member function "const" does not mean it's only for constant objects, it just means that it's callable on constant objects too. Whenever you have a member function that does not modify any member variable, declare it "const" so that errors can be caught by the compiler. It saves you debug time, in addition to making those member functions callable for constant objects (or constant references or pointers.) Next in turn is "top", or rather the two versions of "top":

  int intstack::top(void) const
    throw (stack_underflow, pc_error)
  {
    // Preconditions:
    //   nrOfElements() > 0
    if (nrOfElements() == 0 || pTop == 0)
    {
      throw stack_underflow();
    }
    return pTop->value;
    // Postconditions:
    //   nrOfElements() ==  old nrOfElements()
    // No need to check with this implementation!
  }

  int& intstack::top(void)
     throw (stack_underflow, pc_error)
  {
    // Preconditions:
    //   nrOfElements() > 0
    if (nrOfElements() == 0 || pTop == 0)
    {
      throw stack_underflow();
    }
    return pTop->value;
    // Postconditions:
    //   nrOfElements() ==  old nrOfElements()
    // No need to check with this implementation!
  }
As can be seen, not much differs between the two variants of "top." The implementation is in fact identical for both, but the first one returns a value and is declared const, the other one is not declared const and returns a reference. So why do we have two identical implementations here, when I earlier mentioned that this is always undesirable? The reason is simply that although the implementation is identical, neither can be expressed in terms of the other. The non-const version cannot be implemented with the aid of the const version, since we'd then return a reference to a local value. This is always bad, does not have the desired effect, and quite likely to cause unpredictable run-time behaviour. The "const" version could be implemented in terms of the non-const version, if it wasn't for the fact that it is not declared "const." The implementation of a const member function is not allowed to alter the object, and is, as a consequence of this, not allowed to call non-const member functions for the same object. Remember that a reference really isn't an object on its own? You cannot distinguish it in any way from the object it refers to. In this case it means that what's returned from the non-const version of "top" is the top element itself, not a local copy of it. Since it is the element itself, it can be modified. Note that there is a danger in this too: What about this example?

  intstack is;
  is.push(45);
  int& i=is.top();  // i now refers to the top element
  i=32;             // modify top element.
  int val=is.top(); // val is 32.
  is.pop();         // what does i refer to now?
  i=45;             // what happens here?
The answer to the last two questions is that "i" refers to a variable that no longer exists and that when assigning to it, or getting a value from it, anything can happen. If you're lucky, your program will crash right away, if you're out of luck, it'll start behaving randomly erratically! Now for the copy constructor. With the help of the "copy" member function, it's really simple!

  intstack::intstack(const intstack& i) throw (bad_alloc)
    : pTop(i.copy()),
      elements(i.elements)
  {
    // Preconditions: -
    // Postconditions:
  }
The "pTop" member of the instance being created is initialized with the value from "i.copy()". The "copy" helper function, creates a new copy of i's representation ("pTop" and whatever it points to) on the heap and returns the pointer to its base. If "i" is an empty stack, "copy" returns 0. If we run out of memory when "copy" is working, whatever was allocated will be deallocated, and "bad_alloc" thrown. In this case, it means that "bad_alloc" will be thrown before "pTop" is initialized, and thus the new object will never be constructed. The copy assignment operator is a little bit trickier, but not that bad.

  intstack& intstack::operator=(const intstack& i)
    throw (bad_alloc)
  {
    if (this != &i)
    {
      stack_element* pTmp = i.copy();
        // can throw bad_alloc

      destroyAll(); // guaranteed not to throw!
      pTop=pTmp;
      elements=i.elements;
    }
    return *this;
  }
Seemingly simple, and yet both efficient and exception safe. The difficulty lies in being careful with the order in which to do things. Here a temporary pointer "pTmp" is first set to refer to the copy of "i's" representation. This is very important from an exception handling point of view. Suppose we first destroyed the contents and then tried to get a copy, but the copying threw "bad_alloc." Since we're not catching "bad_alloc", it flows out of the function as intended, but our own "pTop" would point to something illegal, and thus our promise to always stay destructible, and copyable whenever resources allow, would be broken. Instead, first getting the copy is essential. If the copying fails, the member variables are not altered, and the object remains unchanged (whenever possible, try to leave objects in an unaltered state in the presence of exceptions, and always leave them destructible and copyable.) Again, since "bad_alloc" is not caught in the function, it'll flow off to the caller if thrown. If copying is successful, we can safely destroy whatever we have and then change the "pTop" member variable. Since we've promised that "destroyAll" won't throw anything (a promise we could make, since we've promised that our destructors won't throw) the rest is guaranteed to work. Also, since we first get a local copy of the object assigning from, and after that destroy our own representation, the self assignment guard ("if (this != &i)") is not necessary. It's a pure performance boost by making sure we do nothing at all instead of duplicating the representation, just to destroy the original. With the aid of the "destroyAll" helper function, the destructor becomes trivial:

  intstack::~intstack(void)
  {
    destroyAll();
  }
So, how is this magic "destroyAll" helper function implemented? It's actually identical with the old version of the destructor.

  void intstack::destroyAll(void) throw ()
  {
    while (pTop != 0)
    {
      stack_element* p = pTop->pNext;
      delete pTop; // guaranteed not to throw.
      pTop = p;
    }
  }
Now the only thing yet untold is how the helper function "copy" is implemented. It's the by far trickiest function of them all.

  intstack::stack_element*
  intstack::copy(void) const throw (bad_alloc)
  {
    stack_element* pFirst = 0; // used in catch block.
    try {
      stack_element* p = pTop;
      if (p != 0)
      { // take care of first element here.
        stack_element* pPrevious = 0;

        pFirst = new stack_element(p->value,0);
           //     Cannot throw anything except bad_alloc

        if (pFirst == 0) //**1
          throw bad_alloc();
        pPrevious = pFirst;

        // Here we take care of the remaining elements.
        while ((p = p->pNext) != 0) //**2
        {
          pPrevious->pNext =
            new stack_element(p->value,0);
            // cannot throw except bad_alloc

          pPrevious = pPrevious->pNext;
          if (pPrevious == 0)  //**1
            throw bad_alloc();
        }
      }
      return pFirst;
    }
    catch (...) // If anything went wrong, deallocate all
    {           // and rethrow!
      while (pFirst != 0)
      {
        stack_element* pTmp = pFirst->pNext;
        delete pFirst; // guaranteed not to throw.
        pFirst = pTmp;
      }
      throw;
    }
  }
To begin with, the return type is "intstack::stack_element*". The type "stack_element" is only known within "intstack," so whenever used outside of "intstack" it must be explicitly stated that it is the "stack_element" type that is defined in "intstack." As long as we're "in the header" of a member function, nested types must be explicitly stated. Well within the function, it is no longer needed, since it is then known what class the type belongs to. The whole copying is in a "try" block, so we can deallocate things if something goes wrong. The local variable "pFirst", used to point to the first element of the copy, is defined outside of the "try" block, so it can be used inside the "catch" block. If we didn't leave this for the "catch" block, there would be no way it could find the memory to deallocate.
If "pTop" is non-zero, the whole structure that "pTop" refers to is copied.
There are two details worth mentioning here.
  • The "if" statements marked //**1 are only needed for older compilers. New compilers automatically throw "bad_alloc" when they're out of memory. Old compilers, however, return 0.
  • The "while" statement marked //**2 might look odd. What happens is that the variable "p" is given the value of "p->pNext", and that value is compared against zero. Remember that assignment is an expression, and that expression can be used, for example, for comparisons. The assignment "p=p->pNext" must be in a parenthesis for this to work. The precedence rules are such that assignment has lower precedence than comparison, so if we left out the parenthesis, the effect would be to assign "p" the value of "p->pNext" compared to 0, which would not be what we intended.
At the places where a "stack_element" is allocated, it is important that the "pNext" member variable is given the value 0, since it is always put at the end of the stack. If it was not set to 0, it would not be possible to know that it was the last element, and our program would behave erratically. It's not until we have successfully created another element to append to the stack, that the "pNext" member variable is given a value other than 0. Now, it's up to you to toy with the "intstack". Whenever you have a need for a stack of integers, here you have one.

Exercises

  • When is guarding against self assignment necessary? When is it desirable?
  • How can you disallow assignment for instances of a class?
  • The non-const version of "top" returns a reference to data internal to the class. Mail me your reasons for why this can be a bad idea (it can, and usually even is!) Can it be bad in this case?
  • When can returning references be dangerous? When is it not?
  • Mail me an exhaustive list of reasons when assignment or construction can be allowed to fail under the Orthodox Canonical Form.
  • When is it OK to use the auto-generated copy constructor and copy assignment operator?

Recap

This month, yet more news has been introduced to you, as coming C++ programmers.
  • You have seen how C++ references work.
  • You have learned about "const", and how it works for objects.
  • You have seen how you can make member functions callable for "const" objects by declaring them as "const", and seen that member functions declared "const" are callable for non-const objects as well.
  • You have found out how you can overload member functions on "constness" to get different behaviour for const objects and non-const objects.
  • You have learned about the "Orthodox Canonical Form", which always gives you construction from nothing, construction by copying, assignment and destruction.
  • You have learned that your objects should always be in a destructible and copyable state, no matter what happens.
  • You have seen how you can implement common behaviour in private member functions. These member functions are then only callable from within member functions of that class.