[[ This may actually be the “builder” design
pattern in some form ]]
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:
//: C09: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:
//: C09:Bicycle.cpp {O} // Bicycle implementation #include "Bicycle.h" #include <map> #include <algorithm> #include <cassert> 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:
//: C09: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); } ///:~