1        Interfaces

1.1        Overview

This chapter has several goals that are related by the general theme of interface. We begin with the Win32 API, which defines the interface between a C program and a Windows platform. (A pseudo Windows platform is described to give readers a sense of the internal structure of a typical Windows platform.) Next comes a brief overview of user interfaces. Finally, after frameworks are introduced, the MFC framework is defined as the interface between a C++ program and a Windows operating system. The chapter also introduces design patterns, which are used throughout the text to explain MFC designs as well as to under score good programming practices.

1.1.1       Prerequisites

A few UML class and sequence diagrams appear in this chapter. These are reviewed in Appendix 1. Fragments of C++ code also appear. In some cases these fragments involve virtual functions and derived classes. Virtual functions are reviewed in the Programming Notes section at the end of the chapter.

1.2        Platform Services

Every program depends on library functions to access the services provided by the underlying platform[1]. For example, when we read and write data in a C++ program:

cout << "enter two numbers: ";
cin >> x >> y;
cout << "product = " << x * y << endl;

we are making use of cin, cout, endl, operator<<(), and operator>>(), which are defined in libcp.lib, the standard C++ library. These statements interact with the platform to read keyboard input and to display strings in a console window. Fortunately, as its name implies, the standard C++ library is standard. That means we can expect this same segment of code to work on a Unix, MS Windows[2], or Macintosh platform. In other words, the code is platform-independent.

But a platform provides many services that go far beyond keyboard input and console output. Concurrency, graphics, database access, inter-process communication, memory management, and window management are just a few examples. Are these services available through standard libraries? If we are programming in Java, then the answer is yes, although the Java libraries are currently specified by Sun Microsystems rather than some standardizing organization like the ISO. Sadly, for all other languages, including C++, the answer is no. If we want our programs to go beyond simple console and file I/O, then we will have to use non-standard, platform-dependent libraries. If we port our program to a new platform, then all of this code will need to be rewritten.

1.3        The Win32 API

We mentioned the MS Windows platform. Actually, this term is ambiguous. There are several MS Windows platforms, including Windows 95, Windows 98, Windows 2000, Windows NT, and Windows CE. Each provides special libraries of C functions called a Software Development Kit or SDK to access their services. Does this mean an application written for Windows 95 will need to be rewritten to run on a different Windows platform? Probably not. Microsoft has fixed an application programmer interface called the Win32 API. The Win32 API specifies the behavior of more than 1000 library functions, but not their implementations. It is the job of each Windows platform to implement the functions according to their specification.[3] Thus, Windows applications depend on the Win32 API, not on any particular Windows platform:

1.4        Platform Internals

How do platforms implement the Win32 API? This question is beyond the scope of this book; any answer would be at best speculative, because implementation details are not generally available. (See [SOL] for Windows NT internals, however.) Never the less, it will be useful for us to at least have a simple model of how a Windows platform might implement the Win32 API.

Although the implementation language is mostly C, the architecture of Windows XX, our pseudo Windows platform, is object oriented.[4] Thus, threads, windows, files, and other system resources can be regarded as objects. We will call these system objects. We must take care to distinguish system objects from the C++ application objects that will be created and managed by the C++ applications we write. Like any other type of object, a system object encapsulates data, sends and receives messages, and provides services to its clients, the application objects. System objects are instances of system classes. For example, a desktop window object is an instance of a window class. Here again it will be important for us to distinguish between system classes and the C++ application classes that will be declared in the C++ applications we write.

Structurally, Windows XX consists of managers and drivers that pass messages to each other through communication channels provided by the kernel. The drivers control devices such as monitors, disks, and keyboards. Each manager creates, loads, saves, maintains, manipulates, and destroys certain classes of system objects. Thus, the Window Manager manages windows, the File Manager manages files, the Memory Manager manages memory segments, and so on.

The Win32 API functions, which are also written in C, allow applications to send requests to managers to create, manipulate, and destroy system objects. For example, an application may call the Win32 API CreateWindow() function to ask the Window Manager to create a new desktop window:

HWND CreateWindow(
LPCTSTR lpClassName, /* pointer to class name */
LPCTSTR lpWindowName, /* pointer to window name */
DWORD dwStyle, /* window style */
int x, /* horizontal position of window */
int y, /* vertical position of window */
int nWidth, /* window width */
int nHeight, /* window height */
HWND hWndParent, /* handle to parent or owner window */
HMENU hMenu, /* handle to menu or child-window identifier */
Handle hInstance, /* handle to application instance */
LPVHandle lpParam /* pointer to window-creation data */
);

There are many types of windows: application windows, menus, toolbars, dialog boxes, etc. We can think of these window types as system classes. Notice that the first parameter is the name of the system class. Many other parameters are self explanatory.

1.4.1       Handles

How will an application subsequently refer to a system object created on its behalf? If the system object was created in the address space of the application, then the creator function could simply return a pointer to it. But usually the system object won't be in the application's address space or it may reside in a restricted segment, because there may be security problems if an application is given a pointer to a shared or sensitive system object. Instead, the creator function returns a 32 bit "identification number" called a handle, which the application can subsequently use to refer to the system object.

For example, CreateWindow() returns a window handle (HWND). If the application wants to move the window, it passes this handle to the Win32 API MoveWindow() function to tell the window manager which window to move:

BOOL MoveWindow(
HWND hWnd, /* handle of window to move */
int x, /* horizontal position */
int y, /* vertical position */
int nWidth, /* width */
int nHeight, /* height */
BOOL bRepaint /* repaint flag */
);

When the user clicks the [Cancel] or [OK] button on a dialog box, the application calls DestroyWindow() to ask the Window Manager to destroy the window. Here again, the handle is passed as an argument to indicate which window needs to be destroyed:

BOOL DestroyWindow(
HWND hWnd /* handle of window to destroy */
);

The Win32 API is covered in Platform SDK/Win32 API of the MSDN library. [PETZ] and [RICH] are still the principle references on Win32 API programming.

1.5        User Interfaces

As its name implies, a user interface (UI) is a subsystem that mediates between human users and the application core:

1.5.1       Console User Interfaces

Console user interfaces (CUIs) are essentially read-execute-print loops. They perpetually display a prompt or menu in a console window, read a command typed by the user, ask the application to execute the command, then display the result:

while (more)
{
cout << prompt;
cin >> command;
if (command == "quit")
more = false;
else
{
result = Execute(command);
cout << result << endl;
}
}

The Execute() function might use a multi-way conditional statement to determine which function to call:

if (command == "add") result = ExecAdd();
else if (command == "mul") result = ExecMul();
else if (command == "sub") result = ExecSub();
else if (command == "div") result = ExecDiv();
// etc.
else Error("unrecognized command");
return result;

1.5.2       Message Passing

A graphical user interface (GUI) displays controls (menus, buttons, edit boxes, etc.) and views (diagrams, pictures, text, etc.) in an application window. Users operate the controls using the keyboard or the mouse. The operating system senses the mouse and keyboard inputs and sends low-level messages to the application, such as "left mouse button clicked" or "Q key pressed".

Messages are sent to the application by placing them in the application's message queue. A component called the message dispatcher perpetually monitors this queue. When a message arrives, the dispatcher immediately forwards the message to its target object within the application:

This is sometimes referred to as message-driven programming, because the dispatcher, hence the application, is idle if there are no messages to forward. The dispatcher is also called the broker, message loop, or message pump. Here's what the control loop of a typical dispatcher might look like (take a moment to compare it with the CUI control loop given earlier:)

while(more)
{
message = messageQueue.GetMessage(); // idles if empty
if (message->GetType() == QUIT)
more = false;
else
DispatchMessage(message);
}

Each application object capable of receiving messages must implement a message handler member function:

void AppObject::HandleMessage(Message* msg) { ... }

DispatchMessage() determines the target of its input, then passes it to the corresponding message handler:

AppObject* location = find(message->GetTarget());
location->HandleMessage(message);

A message handler might be table-driven or control-driven. A table-driven message handler uses its input message to search a table for the appropriate function to call:

Message

Handler

KEY_PRESSED

HandleKey

MOUSE_CLICKED

HandleMouse

etc.

etc.

A control-driven message handler uses a switch statement to determine the appropriate function to call:

switch(msg->GetType())
{
case (KEY_PRESSED):
HandleKey(msg);
break;
case (MOUSE_CLICKED):
HandleMouse(msg);
break;
// etc.
default: DefaultHandler(msg);
}

In some cases an object might handle a message by creating a new, high-level message, which is then sent via the dispatcher to some other application object. This is accomplished by using a PostMessage() function. For example, an object representing an [OK] button might respond to a MOUSE_CLICKED message by creating an OK_BUTTON_CLICKED message and sending it to an object representing the application window:

PostMessage(
new Message("OK Button", "App Window", OK_BUTTON_CLICKED));

The actually details of message passing will be presented in subsequent chapters.

1.6        Frameworks

A framework is a partially completed application. It provides all of the functionality common to a family of related applications, but leaves out the application domain specific functionality, which must then be provided by various framework customizers. For people who can't afford custom software and whose needs aren't met by off-the-shelf software, it makes economic sense to buy and customize a framework. Also, from a design perspective, it makes sense to separate domain-independent components from domain-dependent components.

There are frameworks for many application domains: finance, healthcare, manufacturing, electronic commerce, telecommunications, information management, workflow management, etc. The most general type of framework is called an application framework. It provides the basic architecture, GUI, and access to platform services for all desktop applications that run on a particular platform. For example, ET++ is a framework for creating X Windows applications, MacApp2 is a framework for creating Macintosh applications, and OWL and MFC are frameworks for creating Windows applications.

1.6.1       How is a framework customized?

Usually, a C++ framework comes as a library of related base classes. Some of the more general base class member functions are implemented, but the member functions that require application-specific knowledge are virtual and must be implemented in derived classes provided by the customization:

1.6.2       SAF, a Simple Application Framework

For example, a simple application framework (SAF) might consist of a dispatcher class with a fully implemented MessageLoop() function, functions for dispatching and posting messages, and functions for registering and un-registering application objects:

class Dispatcher
{
public:
void MessageLoop();
void DispatchMessage(Message* msg);
void PostMessage(Message* msg);
void Register(AppObject* obj);
void UnRegister(AppObject* obj);
// etc.
};

SAF also provides a Message class. Messages contain the addresses of their sender and target objects, the content of the message (such as which key was pressed, which mouse button was clicked, etc.), and a type identification number:

class Message
{
AppObject *sender, *target;
string content;
int type; // id number
// etc.
}

Message types can be predefined integer constants:

#define QUIT 1
#define MOUSE_CLICKED 2
#define KEY_PRESSED 3
#define OK_BUTTON_CLICKED 4
// etc.

As explained earlier, the dispatcher forwards messages to registered application objects, but the framework's AppObject base class declares HandleMessage() as a pure virtual function:

class AppObject
{
public:
virtual void HandleMessage(Message* msg) = 0;
// etc.
protected:
Dispatcher* myDispatcher;
string myName;
};

Every customization of SAF declares various AppObject-derived classes, which must implement the HandleMessage() function before they can be instantiated. For example, a banking customization might represent bank accounts as instances of an AppObject-derived Account class:

class Account: public AppObject
{
public:
void HandleMessage(Message* msg);
// etc.
private:
double balance;
string holder;
int PIN;
void Withdraw(double amt);
void Deposit(double amt);
// etc.
};

The Account::HandleMessage() function might handle high-level messages such as "WITHDRAW $20" or "DEPOSIT $50" by verifying the account holder's name and PIN number, then calling the private Withdraw() or Deposit() member functions.

1.7        MFC, Microsoft Foundation Classes

Microsoft Foundation Classes (MFC) is a framework for developing Windows applications in C++. MFC is more than a framework, it provides a C++ facade to the C-based Win32 API. The MFC library consists of over 200 C++ classes. Most of these classes are organized into a single inheritance hierarchy rooted by the CObject base class:

The actual inheritance hierarchy can be found in the Reference volume of the MSDN Library, and should be added to the MSDN browser's bookmark list:

Despite differences in complexity, there are several similarities between MFC and SAF, the simple application framework defined earlier. For example, MFC provides a message class called MSG that is analogous to SAF's Message class, MFC provides a CCmdTarget class derived from CObject that is analogous to SAF's AppObject class, and MFC provides a CWinApp class derived from CWinThread that is analogous to SAF's Dispatcher class. These classes will be explained in subsequent chapters.

1.7.1       How are MFC classes related to system classes?

There are two important points readers should understand. First, MFC classes are not system classes, they are pre-defined application classes. Second, MFC doesn't replace the Win32 API, it simply hides it. Recall that applications refer to system objects using identification numbers called handles rather than pointers. In fact, most MFC objects encapsulate handles referencing corresponding system objects. For example, every instance of CWnd, the class of all window objects, encapsulates an HWND handle that identifies a corresponding system object that represents a "real" window:

class CWnd : public CCmdTarget
{
public:
HWND m_hWnd;
// etc.
};

Many MFC member functions simply call a corresponding Win32 API global function and pass their encapsulated handles as a parameter:

1.8        Design Patterns

A design pattern is a reusable design that solves a recurring design problem. A pattern catalog is a collection (a book or a web site) of design patterns, usually written in a Problem-Solution format. [Go4] and [POSA] are two good examples of pattern catalogs.

We will occasionally bump into design patterns as we drill down into MFC. When this happens, we will try to point them out. Seeing a pattern makes it easier to distinguish the essence of a design from technological details. Knowing a few patterns can also improve the designs of our own programs.

1.8.1       The Broker Design Pattern

Most design patterns are written in the Problem-Solution format. This format begins by listing the various names the pattern may be known by. Next, an abstract description of the recurring problem is given, followed by a description of a generic, reusable solution.

For example, we have seen that the SAF and MFC frameworks use a message dispatcher to pass messages between objects. In fact, some form of message dispatcher is required whenever message passing is used. Message dispatchers are so common that most of them instantiate the Broker design pattern:

Broker [POSA], [Go4]

Other names

Object request broker, ORB, mediator, dispatcher, event manager, message pump.

Problem

Collaborating objects can communicate by method invocation or by message passing. Although less efficient, message passing allows looser coupling between collaborators. For example, it's easier to separate message passing collaborators by thread or process boundaries. Unfortunately, message passing increases the dependency of collaborators on a particular IPC (Inter-process communication) mechanism and its accompanying message and address formats. Also,
n (n 1)/2 communication "channels" may be required to allow n objects to communicate.

Solution

A broker is the software-equivalent of a bus or a network. Collaborators (which are analogous to computer chips or host nodes) can use a broker to send and receive messages. Collaborators communicate indirectly through a broker, thus, at most n IPC channels will be required. The broker also hides the IPC mechanism, address formats, and message formats from the collaborators.

1.8.1.1            Static Structure

Often, the description of a solution is augmented by a class diagram that displays the static structure of the pattern. For example, the following diagram shows the relationships between collaborators and brokers. Heavy dashed lines in a class or sequence diagram are used to indicate possible thread, process, machine, or network boundaries:

We can identify the Broker class with SAF's Dispatcher class and the abstract Collaborator class with SAF's AppObject class.

1.8.1.2            Dynamic Structure

In addition to the static structure, a design pattern might describe one or two scenarios that show how design pattern classes work together. Often, these scenarios are described using UML sequence diagrams. Because these diagrams show the interaction between objects at runtime, we call this the dynamic structure of the pattern.

For example, the following sequence diagram shows how an instance, a, of Collaborator1 might send message, msg, to an instance, b, of Collaborator2 through an instance of Broker. Upon receiving msg, b computes a response, resp, then sends it back to a through the broker:

In addition, a design pattern typically includes a discussion of its advantages and disadvantages, a section on known uses, and a few implementation hints.[5]

1.8.2       The Wrapper-Body Design Pattern

Why don't MFC programmers deal directly with handles and the Win32 API? The whole idea of MFC is to make the global C functions in the Win32 API appear to be member functions of C++ classes like CWnd. Presumably, this is done so people can write object-oriented Windows programs. This is a common problem. Programmers who want to write object oriented programs are often given non object-oriented "legacy" components to work with. Encapsulating unruly components in well-behaved C++ objects is so common that it is listed in several pattern catalogs:

Wrapper-Body [POSA]

Other Names

Wrappers are known by a variety of names, including handles, envelopes, adapters, and proxies. Bodies may be called states, adaptees, delegates, or letters.

Problem

We need to use a remote object, a shared object, a system object, an object that implements the wrong interface, or a non object such as a C-style data structure. However, we want to shield our clients from the non object-oriented details of managing or communicating with this "object."

Solution

Call the object we want to use a body. Encapsulate a reference, pointer, URL, IP address, or identification number for the body in an object called a wrapper that hides its body's idiosyncrasies from clients. Clients only deal with the wrapper, which transparently forwards or delegates client requests to the body.

1.8.2.1            Static Structure

Structurally, the Wrapper-Body design pattern is simple: client requests are made to a wrapper object, which delegates the request to a hidden body that does most of the work:

1.8.2.2            Dynamic Structure

For example, when a client, c, invokes the ServiceA() method of a wrapper, w, some pre processing of the request may be performed. ServiceX() of the body, b, may be invoked. The wrapper may perform some post processing of the return value, which is then returned to the client. Of course the client has no knowledge of the body:

In this context we can think of CWnd objects as wrappers and windows (i.e., system objects representing windows) as bodies.

1.8.3       The Resource Manager Design Pattern

The managers used by Windows XX, our pseudo Windows platform, create, manage, protect, track, and destroy system objects on behalf of their application object clients. Managers are commonly used to control access to any class of "sensitive" objects. The idea is formalized by the Resource Manager design pattern:

Resource Manager [ROG]

Other Names

Object manager, lifecycle manager

Problem

In some situations we may need to hide the details how certain resource objects are allocated, stored, and deallocated. In addition, we may need to control client access to these objects for security reasons. We may also want to provide "global" operations that can be applied to all current instances of the resource class with a single call.

Solution

A resource manager is responsible for managing instances of an associated class of resource objects. This includes allocating, tracking, controlling, and de-allocating resource objects. Clients cannot access resource objects directly. Instead, they must access these objects indirectly, through the resource manager, using identification numbers provided by the resource manager. In this way, illegal client requests can be detected. In addition, resource managers may aggregate all current instances of the resource, thus providing for global operations such as "save all", statistical information such as "get count", or meta information such as "get properties".

1.8.3.1            Static Structure

Suppose Resource is a "sensitive" class of objects whose services include ServiceA(), ServiceB(), and ServiceC(). We can protect Resource from clients by inserting a Manager class between clients and the Resource class:

In C++, we can protect the Resource class from client access either by declaring all members private (in this case the Manager class is declared to be a friend class) or by making Resource a private inner class of Manager.

1.8.3.2            Dynamic Structure

The following sequence diagram shows how a typical client creates an instance of the Resource class, asks this instance to perform ServiceA(), then destroys it:

1.9        Programming Notes

1.9.1       Polymorphic and Abstract Classes

A pure virtual function is a member function with a partial or missing implementation. In C++ the prototype of a pure virtual function begins with the reserved word "virtual" and ends with "= 0". For example, the AppObject class of SAF, our sketchy application framework, contained a pure virtual function called HandleMessage():

class AppObject
{
public:
virtual void HandleMessage(Message* msg) = 0;
// etc.
};

A class containing one or more pure virtual functions is called an abstract class.

In UML class diagrams, the name of an abstract class is italicized, as are the names of its pure virtual functions:

Abstract classes cannot be instantiated. For example, the declaration:

AppObject* p = new AppObject(); // = a generic app object?

would cause a compiler error, because the compiler worries that the programmer might expect it to be able to translate the statement:

p->HandleMessage(msg); // what should be called?

Instead, programmers must provide implementations of pure virtual functions in derived classes, which can then be instantiated. For example, the Account class in our banking customization of SAF provided an implementation of the pure virtual HandleMessage() function inherited from its AppObject base class:

class Account: public AppObject
{
public:
void HandleMessage(Message* msg)
{
// DEPOSIT & WITHDRAW messages handled here
}
// etc.
};

Programmers may instantiate the Account class:

AppObject* p = new Account(); // AppObject pointers are ok

Of course a C++ compiler still doesn't translate the statement:

p->HandleMessage(msg);

into a call to Account::HandleMessage(). Instead, determination of the function to be called is deferred until the statement is actually executed. This is necessary because p may be pointed at some other type of AppObject that isn't an account before control reaches this statement. The specific type of application object p points to, and therefore the specific implementation of HandleMessage() to be called, can only be known at execution time.

Virtual functions are like pure virtual functions, except they are equipped with a default implementation that derived classes may accept or override. A class containing one or more virtual functions is called a polymorphic class.

If a pure virtual function is a partially implemented member function, then we can think of an abstract class as a partially implemented class. Earlier, we defined a framework to be a partially implemented application. Not surprisingly, abstract classes are common in frameworks.

1.10     Review Problems

1.10.1    Problem

Make a list of all classes derived directly from MFC's CObject class. Class A is directly derived from class B if B is the declared base class of A:

class A: public B { ... };

You will need to consult the hierarchy chart in the MSDN library to answer this question.

1.10.2    Problem

Write down the prototypes of the global FindWindow(), GetTopWindow(), and GetParent() functions in the Win32 API. Give a brief description of what each of these functions does. Hint: you will probably need to use MSDN to answer this question.

1.10.3    Problem

In the banking customization of SAF, we completed the implementation of the pure virtual HandleMessage() function of the AppObject class in a derived class, Account. Alternatively, we could have simply replaced the pure virtual HandleMessage() function in AppObject with the implemented version from the Account class. One advantage of this approach is that programmers will no longer need to worry about implementing AppObject's HandleMessage() function, since it now has a fixed implementation. Why is this a very bad idea?

1.10.4    Problem

How is a framework conceptually similar to an abstract class?

1.11     Programming Problems

Some of the problems in this chapter ask the reader to implement C++ programs. None of these programs require Windows API functions, although extensive knowledge of C++ is required. Start by creating an empty Windows Console project.

1.11.1    Problem

Build a simple framework for console user interfaces. Test your framework by customizing it into a simple calculator that allows users to execute the following commands:

add N M ; displays N + M
mul N M ; displays N * M
div N M ; displays N / M
sub N M ; displays N M

where N and M are arbitrary real numbers.

Here is the design you must follow:

The abstract Context class contains a pure virtual function called Execute(), which expects a command in the form of a C++ string as input, and returns a result in the form of a C++ string:

virtual string Context::Execute(const string& cmmd) = 0;

The console's ControlLoop() function perpetually displays a prompt, reads a command, passes the command to the context's Execute() function, then displays the returned result. The console should call the context's Execute() function inside of a try-catch block in case something goes wrong deep inside the calculator:

try
{
result = myContext->Execute(command);
cout << result << endl;
}
catch(ContextException e)
{
cerr << e.what() << endl;
}

The calculator class simply implements the context's Execute() function. Unrecognized commands and divide by 0 errors should be handled by throwing exceptions back to the console.

1.11.2    Problem

Following the Wrapper-Body design pattern, define and test a class called SafeString. An instance of SafeString is a wrapper that encapsulates a pointer to a body, which in this case is a heap-based, static array of 80 characters:

class SafeString
{
public:
enum { CAPACITY = 80 };
char& GetChar(int i);
void PutChar(int i, char c);
void RemChar(int i);
int GetSize() const;
void AddChar(char c);
void Print(ostream& os = cout) const;
void Read(istream& is = cin);
// etc.
private:
char* body;
int size; // = # of valid chars = next available slot
};

Users can inspect and change individual characters within the array using GetChar(i). Users can insert and remove characters using the PutChar() and RemChar() functions. All three of these functions generate errors if i is out of range. The AddChar() function adds a new character to body[size], assuming space is available. The Print() function inserts the string into an output stream. The Read function extracts the string from an input stream.

There are several trickier problems programmers must solve. For example, each time a safe string is copied or destroyed, the body must also be copied or destroyed. Programmers should also provide a selection of useful constructors.

1.11.3    Problem

Following the Resource Manager design pattern, create and test a resource manager for bank accounts. Instances of the Account class simply encapsulate a balance and provide private Withdraw() and Deposit() member functions:

class Account
{
Money balance;
void Withdraw(Money amt);
void Deposit(Money amt);
// etc.
};

For now, we define Money to be another name for float:

typedef float Money;

In this example a resource manager is called an account manager:

class AccountManager
{
public:
void Withdraw(int id, Money amt);
void Deposit(int id, Money amt);
void Transfer(int source, int dest, Money amt);
Money GetBalance(int id);
int CreateAccount();
void DestroyAccount(int id);
void PrintAll(ostream& os = cout) const;
// etc.
};

Account holders must ask an account manager to create accounts for them. The account manager simply returns the id number it assigns to the newly created account. Holders must use this id number to perform subsequent operations on their accounts. The PrintAll() function prints the id number and balance of each account. All functions generate errors if the account id is invalid or if an account has insufficient funds for a withdrawal.

One problem programmers will face is how to maintain the association between id numbers and accounts. For example, should id numbers be reassigned after their associated account has been destroyed? Programmers should investigate the possibility of using the map<> or vector<> templates from the standard C++ library to solve this problem.

1.11.4    Problem

How could we enhance the account managers from the previous problem to also keep track of account holders and the accounts they hold. For example, we might introduce holder objects that represent account holders:

class Holder
{
string name;
int pin;
// etc.
};

In this case the account manager can prompt the user for a name and pin number before allowing access to their accounts.

bool AccountManager::VerifyHolder(int id)
{
/*
* return true if user knows
* name & pin of account #id
*/
}

1.11.5    Problem

Add SaveAll() and Load functions to the AccountManager class:

void AccountManager::SaveAll(ofstream& f)
{
// save all accounts into file f
}

void AccountManager::Load(ifstream& f)
{
// restore all accounts from file f
}



[1] Programmers use the word "platform" to refer to a computer's hardware and system software (compilers, operating system, etc.)

[2] The MS prefix (which stands for "Microsoft") is sometimes needed to distinguish the MS Windows platform from the X Windows platform.

[3] In truth, the behavior of a function can vary slightly from one Windows platform to another, and some platforms only implement a subset of the Win32 API. Currently, Windows NT implements the largest subset.

[4] Although awkward, it is possible to build object oriented programs in a non-object oriented language, like assembly and C.

[5] This book does not pretend to be a pattern catalog. Interested readers should consult the references cited for each design patter listed to see some of these additional sections.