You are on page 1of 6

DevCentral - Implementing Design Patterns in C++

The original article for this PDF can be found on DevCentral.


http://devcentral.iticentral.com

Implementing Design Patterns in C++


A Tale of Two Iterators

by Jason Shankel

Comment on this article

Introduction

That design patterns are reusable structures in object-oriented systems is among the central themes of Gamma, et al. in their influential book,
Design Patterns [Footnote 1]. In this article, I will explain the concept of design patterns and I will compare the Iterator design pattern
presented in Gamma's book to the implementation of iterators in the Standard Template Library.

A Brief History of Design Patterns

In the early days of object-oriented programming, the object model approach promised to revolutionize software engineering and usher in a
new age of elegant designs and reusable code. As with most technological revolutions, the reality is more complicated than the hype. The
essential components of object-oriented design (data encapsulation, inheritance and polymorphism) are powerful tools but do not, by
themselves, lead to good software design any more than bricks and mortar by themselves lead to good building design.

Invariably in the course of designing an object-oriented system, you will come to a point where understanding the object model is not enough.
How are objects created? How are they initialized? Who owns object instances? How are object instances accessed? Who is responsible for
cleaning up objects?

Answering these questions is where design patterns come in. Design patterns are formal descriptions of reusable object relationships. By
analogy, design patterns are to object-oriented programming what algorithms are to procedural programming. The basic elements of
procedural programming (step instruction, conditionals, and branching) aren't very useful without concepts like linked list and quicksort to give
them some structure.

But what, exactly, is a design pattern?

Consider a graphical bitmap class Bitmap. Suppose you want Bitmap to support a variety of graphics file formats (GIF, JPG, BMP, etc). One
approach would be to add a member function to Bitmap for each file type:

class Bitmap
{
public:
void ReadBMP(const char *filename);
void ReadJPG(const char *filename);
void ReadGIF(const char *filename);
...
};

http://devcentral.iticentral.com/articles/C++/patterns/default.php (1 of 6) [7/9/2001 2:47:04 PM]


DevCentral - Implementing Design Patterns in C++

This method has a number of drawbacks. First, as the number of supported file formats increases the Bitmap interface becomes increasingly
cluttered. Second, supporting new file formats is difficult or undesirable since it requires modification of the Bitmap class itself. Suppose you
want Bitmap to support an application-specific file format. Modifying Bitmap to support this format would pollute the interface.

A better solution to this problem would be to divorce file parsing from the Bitmap class by defining a second class hierarchy, BitmapBuilder:

class BitmapBuilder
{
public:
virtual void ReadFromFile(Bitmap *,
const char *filename) = 0;
};

To support new file formats, all you need to do is inherit from BitmapBuilder and implement the ReadFromFile () member. JPGBuilder reads
JPG files, BMPBuilder reads BMP files, etc. Now you can support new file types and private file formats without modifying the Bitmap interface.

This concept, separating a class interface from an initialization interface, is the Builder design pattern.

To be sure, this structure existed long before Gamma et al. attached the name "Builder" to it. But, giving a complex concept like this a simple
name helps us develop a powerful vocabulary for object designs.

Furthermore, examining the structure of Builder helps us to recognize that the problem of supporting multiple file formats has much in
common with the problem of, say, creating a new word processor document from a template or generating a random maze layout for a level-
based game.

Attaching the name "Builder" to this object structure is akin to attaching the name "quicksort" to an O(nlog(n)) divide-and-conquer sorting
algorithm. You don't have to re-explain it every time you use it.

Design Patterns and C++

In their book, Gamma et al. limit themselves to the traditional elements of object-oriented languages, namely inheritance and virtual functions.
While these are powerful tools, they are not the only tools available to the C++ programmer.

To illustrate how traditional object-oriented languages and C++ can take two separate paths to arrive at the same destination, I will compare
the Iterator design pattern described in Gamma's book to the implementation of iterators provided in the Standard Template Library.

The Iterator Design Pattern

Consider a class library that implements a variety of containers (lists, vectors, binary trees, etc). Containers inherit from an abstract container
class:

template<class Item>
class Container
{
public:
void AddItem(const Item &item) = 0;
void RemoveItem (const Item &item) = 0;
};

List, Vector, Deque, Tree, etc all inherit from Container and implement the AddItem() and RemoveItem() members.

http://devcentral.iticentral.com/articles/C++/patterns/default.php (2 of 6) [7/9/2001 2:47:04 PM]


DevCentral - Implementing Design Patterns in C++

Suppose you want clients to be able to access the elements of these container classes without knowing what kind of container is being used.
The Iterator design pattern, as described in the book, provides an approach for solving this problem using inheritance and virtual functions.
The idea is to use a second object type, Iterator, which provides access to elements in a container.

The abstract Iterator class defines the interface clients use to access the elements of a container:

template <class Item>


class Iterator
{
public:
virtual Item &CurrentItem()=0;
virtual void GotoFirst()=0;
virtual void GotoNext()=0;
virtual bool IsDone()=0;
};

CurrentItem() returns the item currently pointed to by the Iterator, GotoFirst() sets the Iterator to the beginning of the container, GotoNext()
advances the Iterator to the next item in the container and IsDone() returns true if the Iterator has been advanced past the last element in the
container.

Concrete iterators for each container inherit from Iterator. ListIterator, VectorIterator, TreeIterator, DequeIterator, etc. each implement the
Iterator interface for their respective containers.

Since clients won't necessarily know what kind of containers they're using, they won't necessarily know what kind of iterators to create. To
solve this, we add a CreateIterator() member to Container:

template <class Item>


class Container
{
public:
Iterator<Item> *CreateIterator() = 0;
...
};

Concrete containers implement CreateIterator() to return the correct iterator type:

template <class Item>


class List : public Container<Item>
{
public:
Iterator<Item> *CreateIterator()
{
return new ListIterator<Item>();
}
...
};

So, how do we use these iterators? A function for printing out every element in a container would look something like this:

http://devcentral.iticentral.com/articles/C++/patterns/default.php (3 of 6) [7/9/2001 2:47:04 PM]


DevCentral - Implementing Design Patterns in C++

template<class Item>
void PrintElements(Container<Item> *container)
{
Iterator<Item> *it = container->CreateIterator();
it->GotoFirst();
while(!it->IsDone())
{
cout << it->CurrentItem() << endl;
it->GotoNext();
}
delete it;
}

This approach has a number of advantages, not the least of which is that you can write PrintElements() once and it will work for any and all
future containers. It also has a few drawbacks. First, every future client of PrintElements() must inherit from Container. Second,
PrintElements() makes three virtual function calls in its inner loop. For an application like printing, this overhead probably won't amount to
much, but for more performance-intensive loops it can be problematic.

It would be desirable to find a way to preserve the generic aspects of this approach while relieving ourselves of the structural and performance
burden of the abstract interface.

STL Iterators

STL iterators provide generic sequential access to their containers without using virtual functions or inheritance. How? The fundamental insight
that allows the STL to do this is the realization that C++, in fact C, comes with iterators already built into the language. They're called pointers.

Consider the following C++ function:

template <class Item>


void PrintArray(Item *array, size_t len)
{
Item *it = &array[0];
while (it!=&array[len])
{
cout << *it << endl;
++it;
}
}

This function has the same essential structure as the PrintElements() function presented in the previous section. The "iterator" starts at the
beginning of the array and is incremented until it reaches the end of the array. Except this PrintElements() works without using virtual
functions and without requiring the user to inherit from anything.

The only problem is that it only works for built-in arrays. This approach doesn't work for linked lists or binary trees. Or does it?

Often, object-oriented design is motivated by the desire to elevate nasty near machine-level concepts like arrays and pointers to higher-level,
more abstract concepts like containers and iterators. STL iterators take the opposite approach. STL iterators use operator overloading to make
high-level objects appear and behave just like built-in pointers. The ++ and -- operators are used to move to the next and previous elements
in a sequence and the * operator is used to access data.

The STL version of PrintElements() would look like this:

http://devcentral.iticentral.com/articles/C++/patterns/default.php (4 of 6) [7/9/2001 2:47:04 PM]


DevCentral - Implementing Design Patterns in C++

template<class Iterator>
void PrintElements(Iterator first, Iterator last)
{
while(first != last)
{
cout << *first << endl;
++first;
}
}

This approach places no requirements on the iterator except that it support the ++, != and * operators. No inheritance and no virtual functions
are required.

Notice how we've changed the PrintElements() interface, just slightly. Instead of passing in a container, we pass in a first and last iterator. This
change means that PrintElements() not only supports STL containers, but built-in C arrays as well. In fact, PrintElements() will work for any
object that supports ++, != and *.

A Word About Templates

Both the inheritance-based and STL versions of PrintElements() presented here use templates. The difference is that the former specializes
based on the type stored in the container and the latter specializes based on the type of the iterator.

The consequence of this difference is that a system that uses a vector, list and deque of integers would produces only a single version of
PrintElements() in the inheritance-based version (specialized for Container<int>), but would produce three versions in the STL case (one for
each of the three iterator types).

This is the classic size versus speed tradeoff. Virtual functions produce smaller code at the cost of decreased performance. Templates provide
greater flexibility and speed at the cost of larger code.

In the world of PC development, code bloat isn't much of a concern. Typical systems today have dozens, if not hundreds of megabytes of RAM
and even large executables top out under four or five megabytes.

The real concern with templates is the fact that template functions must be declared inline. Anyone who's ever worked on a large-scale C or
C++ project knows how sensitive compile times can be to even minor changes in header files.

Parting Thoughts

In the study of algorithmics, common wisdom dictates that understanding algorithms is what's important; implementations are trivial. The
abundance of mediocre implementations and the remarkable success of the Standard Template Library suggests that quality of implementation
is more important than common wisdom suggests.

One of the keys to effective implementation of design patterns in C++ is recognizing that class inheritance is not the only form of
polymorphism available in the language. Templates and function overloading provide the same degree of flexibility at compile time that virtual
functions provide at runtime.

Object-oriented designs are destinations, not paths. Finding the right path to your destination is what distinguishes elegant implementations
from merely adequate ones.

http://devcentral.iticentral.com/articles/C++/patterns/default.php (5 of 6) [7/9/2001 2:47:04 PM]


DevCentral - Implementing Design Patterns in C++

1. Design Patterns: Elements of Reusable Object-Oriented


Software. By Erich Gamma, et al., Addison-Wesley, 1995.
(ISBN: 0201633612)

© 2001 Interface Technologies, Inc. All Rights Reserved


Questions or Comments? devcentral@iticentral.com
PRIVACY POLICY

http://devcentral.iticentral.com/articles/C++/patterns/default.php (6 of 6) [7/9/2001 2:47:04 PM]

You might also like