|
|
|
|
Why iterators?
Up until now you’ve seen the
mechanics of iterators, but understanding why they are so important takes a more
complex example.
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 if 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). As the final code in Volume 1 of
this book, this example will also pull together various aspects of everything
you’ve learned so far – if you can follow this example, then
you’re ready for Volume 2.
Suppose you are creating a program that
allows the user to edit and produce different kinds of drawings. Each drawing is
an object that contains a collection of Shape objects:
//: C16:Shape.h
#ifndef SHAPE_H
#define SHAPE_H
#include <iostream>
#include <string>
class Shape {
public:
virtual void draw() = 0;
virtual void erase() = 0;
virtual ~Shape() {}
};
class Circle : public Shape {
public:
Circle() {}
~Circle() { std::cout << "Circle::~Circle\n"; }
void draw() { std::cout << "Circle::draw\n";}
void erase() { std::cout << "Circle::erase\n";}
};
class Square : public Shape {
public:
Square() {}
~Square() { std::cout << "Square::~Square\n"; }
void draw() { std::cout << "Square::draw\n";}
void erase() { std::cout << "Square::erase\n";}
};
class Line : public Shape {
public:
Line() {}
~Line() { std::cout << "Line::~Line\n"; }
void draw() { std::cout << "Line::draw\n";}
void erase() { std::cout << "Line::erase\n";}
};
#endif // SHAPE_H ///:~
This uses the classic structure of
virtual functions in the base class that are overridden in the derived class.
Notice that the Shape class includes a virtual
destructor, something you should
automatically add to any class with virtual functions. If a container
holds pointers or references to Shape objects, then when the
virtual destructors are called for those objects everything will be
properly cleaned up.
Each different type of drawing in the
following example makes use of a different kind of templatized container class:
the PStash and Stack that have been defined in this chapter, and
the vector class from the Standard C++ Library.
The “use”’ of the containers is extremely simple, and in
general inheritance might not be
the best approach (composition could make more sense), but in this case
inheritance is a simple approach and it doesn’t detract from the point
made in the example.
//: C16:Drawing.cpp
#include <vector> // Uses Standard vector too!
#include "TPStash2.h"
#include "TStack2.h"
#include "Shape.h"
using namespace std;
// A Drawing is primarily a container of Shapes:
class Drawing : public PStash<Shape> {
public:
~Drawing() { cout << "~Drawing" << endl; }
};
// A Plan is a different container of Shapes:
class Plan : public Stack<Shape> {
public:
~Plan() { cout << "~Plan" << endl; }
};
// A Schematic is a different container of Shapes:
class Schematic : public vector<Shape*> {
public:
~Schematic() { cout << "~Schematic" << endl; }
};
// A function template:
template<class Iter>
void drawAll(Iter start, Iter end) {
while(start != end) {
(*start)->draw();
start++;
}
}
int main() {
// Each type of container has
// a different interface:
Drawing d;
d.add(new Circle);
d.add(new Square);
d.add(new Line);
Plan p;
p.push(new Line);
p.push(new Square);
p.push(new Circle);
Schematic s;
s.push_back(new Square);
s.push_back(new Circle);
s.push_back(new Line);
Shape* sarray[] = {
new Circle, new Square, new Line
};
// The iterators and the template function
// allow them to be treated generically:
cout << "Drawing d:" << endl;
drawAll(d.begin(), d.end());
cout << "Plan p:" << endl;
drawAll(p.begin(), p.end());
cout << "Schematic s:" << endl;
drawAll(s.begin(), s.end());
cout << "Array sarray:" << endl;
// Even works with array pointers:
drawAll(sarray,
sarray + sizeof(sarray)/sizeof(*sarray));
cout << "End of main" << endl;
} ///:~
The different types of containers all
hold pointers to Shape and pointers to upcast objects of classes derived
from Shape. However, because of polymorphism, the
proper behavior still occurs when the virtual functions
are called.
Note that sarray, the array
of Shape*, can also be thought of as a
container.
|
|
|