Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Polymorphism & containers

It’s common to see polymorphism, dynamic object creation and containers used together in a true object-oriented program. Containers and dynamic object creation solve the problem of not knowing how many or what type of objects you’ll need, and because the container is configured to hold pointers to base-class objects, an upcast occurs every time you put a derived-class pointer into the container (with the associated code organization and extensibility benefits). The following example is a little simulation of trash recycling. All the trash is put into a single bin, then later it’s sorted out into separate bins. There’s a function that goes through any trash bin and figures out what the resource value is. Notice this is not the most elegant way to implement this simulation; the example will be revisited in Chapter XX when Run-Time Type Identification (RTTI) is explained:

//: C16:Recycle.cpp
// Containers & polymorphism
#include "TStack.h"
#include <fstream>
#include <cstdlib>
#include <ctime>
using namespace std;
ofstream out("recycle.out");

enum TrashType { AluminumT, PaperT, GlassT };

class Trash {
  float _weight;
public:
  Trash(float wt) : _weight(wt) {}
  virtual TrashType trashType() const = 0;
  virtual const char* name() const = 0;
  virtual float value() const = 0;
  float weight() const { return _weight; }
  virtual ~Trash() {}
};

class Aluminum : public Trash {
  static float val;
public:
  Aluminum(float wt) : Trash(wt) {}
  TrashType trashType() const { return AluminumT; }
  virtual const char* name() const {
    return "Aluminum";
  }
  float value() const { return val; }
  static void value(int newval) {
    val = newval;
  }
};

float Aluminum::val = 1.67;

class Paper : public Trash {
  static float val;
public:
  Paper(float wt) : Trash(wt) {}
  TrashType trashType() const { return PaperT; }
  virtual const char* name() const {
    return "Paper";
  }
  float value() const { return val; }
  static void value(int newval) {
    val = newval;
  }
};

float Paper::val = 0.10;

class Glass : public Trash {
  static float val;
public:
  Glass(float wt) : Trash(wt) {}
  TrashType trashType() const { return GlassT; }
  virtual const char* name() const {
    return "Glass";
  }
  float value() const { return val; }
  static void value(int newval) {
    val = newval;
  }
};

float Glass::val = 0.23;

// Sums up the value of the Trash in a bin:
void sumValue(const TStack<Trash>& bin,ostream& os){
  TStackIterator<Trash> tally(bin);
  float val = 0;
  while(tally) {
    val += tally->weight() * tally->value();
    os << "weight of " << tally->name()
        << " = " << tally->weight() << endl;
    tally++;
  }
  os << "Total value = " << val << endl;
}

int main() {
  srand(time(0)); // Seed random number generator
  TStack<Trash> bin; // Default to ownership
  // Fill up the Trash bin:
  for(int i = 0; i < 30; i++)
    switch(rand() % 3) {
      case 0 :
        bin.push(new Aluminum(rand() % 100));
        break;
      case 1 :
        bin.push(new Paper(rand() % 100));
        break;
      case 2 :
        bin.push(new Glass(rand() % 100));
        break;
    }
  // Bins to sort into:
  TStack<Trash> glassBin(0); // No ownership
  TStack<Trash> paperBin(0);
  TStack<Trash> alBin(0);
  TStackIterator<Trash> sorter(bin);
  // Sort the Trash:
  // (RTTI offers a nicer solution)
  while(sorter) {
    // Smart pointer call:
    switch(sorter->trashType()) {
      case AluminumT:
        alBin.push(sorter.current());
        break;
      case PaperT:
        paperBin.push(sorter.current());
        break;
      case GlassT:
        glassBin.push(sorter.current());
        break;
    }
    sorter++;
  }
  sumValue(alBin, out);
  sumValue(paperBin, out);
  sumValue(glassBin, out);
  sumValue(bin, out);
} ///:~

This uses the classic structure of virtual functions in the base class that are redefined in the derived class. The container TStack is instantiated for Trash, so it holds Trash pointers, which are pointers to the base class. However, it will also hold pointers to objects of classes derived from Trash, as you can see in the call to push( ). When these pointers are added, they lose their specific identities and become simply Trash pointers (they are upcast). However, because of polymorphism the proper behavior still occurs when the virtual function is called through the tally and sorter iterators. (Notice the use of the iterator’s smart pointer, which causes the virtual function call.)

The Trash class also includes a virtual destructor, something you should automatically add to any class with virtual functions. When the bin container goes out of scope, the container’s destructor calls all the virtual destructors for the objects it contains, and thus properly cleans everything up.

Because container class templates are rarely subject to the inheritance and upcasting you see with “ordinary” classes, you’ll almost never see virtual functions in these types of classes. Their reuse is implemented with templates, not with inheritance.

Contents | Prev | Next


Contact: webmaster@codeguru.com
CodeGuru - the website for developers.
[an error occurred while processing this directive]