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

Dynamic aggregation

The examples we’ve seen so far are illustrative, but fairly simple. It’s useful to see an example that has more complexity so you can see that the STL will work in all situations.

[[ Add a factory method that takes a vector of string]]

The class that will be created as the example will be reasonably complex: it’s a bicycle which can have a choice of parts. In addition, you can change the parts during the lifetime of a Bicycle object; this includes the ability to add new parts or to upgrade from standard-quality parts to “fancy” parts. The BicyclePart class is a base class with many different types, and the Bicycle class contains a vector<BicyclePart*> to hold the various combination of parts that may be attached to a Bicycle:

//: CXX:Bicycle.h
// Complex class involving dynamic aggregation
#ifndef BICYCLE_H
#define BICYCLE_H
#include <vector>
#include <string>
#include <iostream>
#include <typeinfo>

class LeakChecker {
  int count;
public:
  LeakChecker() : count(0) {}
  void print() {
    std::cout << count << std::endl; 
  }
  ~LeakChecker() { print(); }
  void operator++(int) { count++; }
  void operator--(int) { count--; }
};

class BicyclePart {
  static LeakChecker lc;
public:
  BicyclePart() { lc++; }
  virtual BicyclePart* clone() = 0;
  virtual ~BicyclePart() { lc--; }
  friend std::ostream& 
  operator<<(std::ostream& os, BicyclePart* bp) {
    return os << typeid(*bp).name();
  }
  friend class Bicycle;
};

enum BPart {
  Frame, Wheel, Seat, HandleBar, 
  Sprocket, Deraileur,
};

template<BPart id> 
class Part : public BicyclePart {
public:
  BicyclePart* clone() { return new Part<id>; }
};

class Bicycle {
public:
  typedef std::vector<BicyclePart*> VBP;
  Bicycle();
  Bicycle(const Bicycle& old);
  Bicycle& operator=(const Bicycle& old);
  // [Other operators as needed go here:]
  // [...]
  // [...]
  ~Bicycle() { purge(); }
  // So you can change parts on a bike (but be 
  // careful: you must clean up any objects you
  // remove from the bicycle!)
  VBP& bikeParts() { return parts; }
  friend std::ostream& 
  operator<<(std::ostream& os, Bicycle* b);
  static void print(std::vector<Bicycle*>& vb, 
    std::ostream& os = std::cout);
private:
  static int counter;
  int id;
  VBP parts;
  void purge();
};

// Both the Bicycle and the generator should 
// provide more variety than this. But this gives
// you the idea.
struct BicycleGenerator {
  Bicycle* operator()() {
    return new Bicycle();
  }
}; 
#endif // BICYCLE_H ///:~ 

The operator<< for ostream and Bicycle moves through and calls the operator<< for each BicyclePart, and that prints out the class name of the part so you can see what a Bicycle contains. The BicyclePart::clone( ) member function is necessary in the copy-constructor of Bicycle, since it just has a vector<BicyclePart*> and wouldn’t otherwise know how to copy the BicycleParts correctly. The cloning process, of course, will be more involved when there are data members in a BicyclePart.

BicyclePart::partcount is used to keep track of the number of parts created and destroyed (so you can detect memory leaks). It is incremented every time a new BicyclePart is created and decremented when one is destroyed; also, when partcount goes to zero this is reported and if it goes below zero there will be an assert( ) failure.

If you want to change BicycleParts on a Bicycle, you just call Bicycle::bikeParts( ) to get the vector<BicyclePart*> which you can then modify. But whenever you remove a part from a Bicycle, you must call delete for that pointer, otherwise it won’t get cleaned up.

Here’s the implementation:

//: CXX:Bicycle.cpp {O}
// Bicycle implementation
#include <map>
#include <algorithm>
#include <cassert>
#include "Bicycle.h"
using namespace std;

// Static member definitions:
LeakChecker BicyclePart::lc;
int Bicycle::counter = 0;

Bicycle::Bicycle() : id(counter++) {
  BicyclePart *bp[] = {
    new Part<Frame>, 
    new Part<Wheel>, new Part<Wheel>, 
    new Part<Seat>, new Part<HandleBar>,
    new Part<Sprocket>,  new Part<Deraileur>,
  };
  const int bplen = sizeof bp / sizeof *bp;
  parts = VBP(bp, bp + bplen);
}

Bicycle::Bicycle(const Bicycle& old) 
  : parts(old.parts.begin(), old.parts.end()) {
  for(int i = 0; i < parts.size(); i++)
    parts[i] = parts[i]->clone();
}

Bicycle& Bicycle::operator=(const Bicycle& old) {
  purge(); // Remove old lvalues
  parts.resize(old.parts.size());
  copy(old.parts.begin(), 
    old.parts.end(), parts.begin());
  for(int i = 0; i < parts.size(); i++)
    parts[i] = parts[i]->clone();
  return *this;
}

void Bicycle::purge() {
  for(VBP::iterator it = parts.begin();
    it != parts.end(); it++) {
      delete *it;
      *it = 0; // Prevent multiple deletes
  }
}

ostream& operator<<(ostream& os, Bicycle* b) {
  copy(b->parts.begin(), b->parts.end(),
    ostream_iterator<BicyclePart*>(os, "\n"));
  os << "--------" << endl;
  return os;
}

void Bicycle::print(vector<Bicycle*>& vb, 
  ostream& os) {
  copy(vb.begin(), vb.end(),
    ostream_iterator<Bicycle*>(os, "\n"));
  cout << "--------" << endl;
} ///:~ 

Here’s a test:

//: CXX:BikeTest.cpp
//{L} Bicycle
#include "Bicycle.h"
#include <algorithm>
using namespace std;

int main() {
  vector<Bicycle*> bikes;
  BicycleGenerator bg;
  generate_n(back_inserter(bikes), 12, bg);
  Bicycle::print(bikes);
} ///:~ 

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru