Here is ShapeFactory1.cpp modified so the Factory Methods are in a separate class as virtual functions: Comment virtual void draw = 0; virtual void erase = 0; virtual ~Shape {} }; cl
Trang 1virtual void draw() = 0;
virtual void erase() = 0;
class Circle : public Shape {
Circle() {} // Private constructor
friend class Shape;
public:
void draw() { cout << "Circle::draw\n"; }
void erase() { cout << "Circle::erase\n"; }
~Circle() { cout << "Circle::~Circle\n"; }
void draw() { cout << "Square::draw\n"; }
void erase() { cout << "Square::erase\n"; }
~Square() { cout << "Square::~Square\n"; }
};
Shape* Shape::factory(const string& type)
throw(Shape::BadShapeCreation) {
if(type == "Circle") return new Circle;
if(type == "Square") return new Square;
throw BadShapeCreation(type);
}
char* shlist[] = { "Circle", "Square", "Square",
"Circle", "Circle", "Circle", "Square", "" };
Trang 2(The initialization data for the objects will presumably come from somewhere outside the system and will not be a hard-coded array as in the previous example.) Comment
To ensure that the creation can only happen in the factory( ), the constructors for the specific types of Shape are made private, and Shape is declared a friend so that factory( ) has access
to the constructors (You could also declare only Shape::factory( ) to be a friend, but it seems reasonably harmless to declare the entire base class as a friend.) Comment
Polymorphic factories
The static factory( ) member function in the previous example forces all the creation operations
to be focused in one spot, so that’s the only place you need to change the code This is certainly a
reasonable solution, as it nicely encapsulates the process of creating objects However, Design
Patterns emphasizes that the reason for the Factory Method pattern is so that different types of
factories can be derived from the basic factory Factory Method is in fact a special type of
polymorphic factory However, Design Patterns does not provide an example, but instead just
repeats the example used for the Abstract Factory Here is ShapeFactory1.cpp modified so the
Factory Methods are in a separate class as virtual functions: Comment
virtual void draw() = 0;
virtual void erase() = 0;
virtual ~Shape() {}
};
class ShapeFactory {
virtual Shape* create() = 0;
static map<string, ShapeFactory*> factories;
public:
virtual ~ShapeFactory() {}
friend class ShapeFactoryInitializer;
class BadShapeCreation : public logic_error {
public:
Trang 3class Circle : public Shape {
Circle() {} // Private constructor
public:
void draw() { cout << "Circle::draw\n"; }
void erase() { cout << "Circle::erase\n"; }
~Circle() { cout << "Circle::~Circle\n"; }
private:
friend class ShapeFactoryInitializer;
class Factory;
friend class Factory;
class Factory : public ShapeFactory {
public:
Shape* create() { return new Circle; }
friend class ShapeFactoryInitializer;
void draw() { cout << "Square::draw\n"; }
void erase() { cout << "Square::erase\n"; }
~Square() { cout << "Square::~Square\n"; }
private:
friend class ShapeFactoryInitializer;
class Factory;
friend class Factory;
class Factory : public ShapeFactory {
public:
Shape* create() { return new Square; }
friend class ShapeFactoryInitializer;
Trang 4char* shlist[] = { "Circle", "Square", "Square",
"Circle", "Circle", "Circle", "Square", "" };
Now the Factory Method appears in its own class, ShapeFactory, as the virtual create( ) This
is a private member function, which means it cannot be called directly but can be overridden The
subclasses of Shape must each create their own subclasses of ShapeFactory and override the create( ) member function to create an object of their own type These factories are private, so
that they are only accessible from the main Factory Method This way, all client code must go through the Factory Method in order to create objects Comment
The actual creation of shapes is performed by calling ShapeFactory::createShape( ), which is
a static member function that uses the map in ShapeFactory to find the appropriate factory
object based on an identifier that you pass it The factory is immediately used to create the shape object, but you could imagine a more complex problem in which the appropriate factory object is returned and then used by the caller to create an object in a more sophisticated way However, it seems that much of the time you don’t need the intricacies of the polymorphic Factory Method,
and a single static member function in the base class (as shown in ShapeFactory1.cpp) will
work fine Comment
Notice that the ShapeFactory must be initialized by loading its map with factory objects, which takes place in the Singleton ShapeFactoryInitializer So to add a new type to this design you must define the type, create a factory, and modify ShapeFactoryInitializer so that an instance
of your factory is inserted in the map This extra complexity again suggests the use of a static
Factory Method if you don’t need to create individual factory objects Comment
Abstract factories
The Abstract Factory pattern looks like the factory objects we’ve seen previously, with not one but several Factory Methods Each of the Factory Methods creates a different kind of object The idea
is that when you create the factory object, you decide how all the objects created by that factory
will be used The example in Design Patterns implements portability across various graphical user
interfaces (GUIs): you create a factory object appropriate to the GUI that you’re working with, and
Trang 5from then on when you ask it for a menu, a button, a slider, and so on, it will automatically create the appropriate version of that item for the GUI Thus, you’re able to isolate, in one place, the effect of changing from one GUI to another Comment
As another example, suppose you are creating a general-purpose gaming environment and you want to be able to support different types of games Here’s how it might look using an Abstract Factory: Comment
class Kitty: public Player {
virtual void interactWith(Obstacle* ob) {
cout << "Kitty has encountered a ";
ob->action();
}
};
class KungFuGuy: public Player {
virtual void interactWith(Obstacle* ob) {
cout << "KungFuGuy now battles against a ";
virtual Player* makePlayer() = 0;
virtual Obstacle* makeObstacle() = 0;
virtual Player* makePlayer() {
return new Kitty;
Trang 6}
virtual Obstacle* makeObstacle() {
return new Puzzle;
virtual Player* makePlayer() {
return new KungFuGuy;
}
virtual Obstacle* makeObstacle() {
return new NastyWeapon;
Kitty has encountered a Puzzle
KungFuGuy now battles against a NastyWeapon */ ///:~
In this environment, Player objects interact with Obstacle objects, but the types of players and
obstacles depend on the game You determine the kind of game by choosing a particular
GameElementFactory, and then the GameEnvironment controls the setup and play of the
game In this example, the setup and play are simple, but those activities (the initial conditions
and the state change) can determine much of the game’s outcome Here, GameEnvironment is
not designed to be inherited, although it could possibly make sense to do that Comment
This example also illustrates double dispatching, which will be explained later.
Virtual constructors
One of the primary goals of using a factory is to organize your code so you don’t have to select an exact type of constructor when creating an object That is, you can say, “I don’t know precisely what type of object you are, but here’s the information Create yourself.” Comment
Trang 7yp j y , y
In addition, during a constructor call the virtual mechanism does not operate (early binding
occurs) Sometimes this is awkward For example, in the Shape program it seems logical that inside the constructor for a Shape object, you would want to set everything up and then draw( ) the Shape The draw( ) function should be a virtual function, a message to the Shape that it
should draw itself appropriately, depending on whether it is a circle, a square, a line, and so on However, this doesn’t work inside the constructor, virtual functions resolve to the “local” function bodies when called in constructors Comment
If you want to be able to call a virtual function inside the constructor and have it do the right
thing, you must use a technique to simulate a virtual constructor (which is a variation of the
Factory Method) This is a conundrum Remember, the idea of a virtual function is that you send a message to an object and let the object figure out the right thing to do But a constructor builds an object So a virtual constructor would be like saying, “I don’t know exactly what type of object you are, but build yourself anyway.” In an ordinary constructor, the compiler must know which
VTABLE address to bind to the VPTR, and if it existed, a virtual constructor couldn’t do this because it doesn’t know all the type information at compile time It makes sense that a constructor can’t be virtual because it is the one function that absolutely must know everything about the type
of the object Comment
And yet there are times when you want something approximating the behavior of a virtual
constructor
In the Shape example, it would be nice to hand the Shape constructor some specific information
in the argument list and let the constructor create a specific type of Shape (a Circle, Square) with no further intervention Ordinarily, you’d have to make an explicit call to the Circle,
Square constructor yourself Comment
Coplien calls his solution to this problem “envelope and letter classes.” The “envelope” class is the base class, a shell that contains a pointer to an object of the base class The constructor for the
“envelope” determines (at runtime, when the constructor is called, not at compile time, when the type checking is normally done) what specific type to make, creates an object of that specific type (on the heap), and then assigns the object to its pointer All the function calls are then handled by the base class through its pointer So the base class is acting as a proxy for the derived class:
virtual void draw() { s->draw(); }
virtual void erase() { s->erase(); }
virtual void test() { s->test(); };
virtual ~Shape() {
cout << "~Shape\n";
if(s) {
[122]
Trang 8cout << "Making virtual call: ";
s->erase(); // Virtual call
Circle() {} // Private constructor
friend class Shape;
public:
void draw() { cout << "Circle::draw\n"; } void erase() { cout << "Circle::erase\n"; } void test() { draw(); }
~Circle() { cout << "Circle::~Circle\n"; }};
class Square : public Shape {
~Square() { cout << "Square::~Square\n"; }};
else throw BadShapeCreation(type);
draw(); // Virtual call in the constructor}
char* shlist[] = { "Circle", "Square", "Square", "Circle", "Circle", "Circle", "Square", "" };int main() {
vector<Shape*> shapes;
cout << "virtual constructor calls:" << endl; try {
Trang 9Shape c("Circle"); // Create on the stack
cout << "destructor calls:" << endl;
The base class Shape contains a pointer to an object of type Shape as its only data member
When you build a “virtual constructor” scheme, exercise special care to ensure this pointer is always initialized to a live object Comment
Each time you derive a new subtype from Shape, you must go back and add the creation for that type in one place, inside the “virtual constructor” in the Shape base class This is not too onerous
a task, but the disadvantage is you now have a dependency between the Shape class and all
classes derived from it (a reasonable trade-off, it seems) Also, because it is a proxy, the base-class interface is truly the only thing the user sees Comment
In this example, the information you must hand the virtual constructor about what type to create
is explicit: it’s a string that names the type However, your scheme can use other information—
for example, in a parser the output of the scanner can be handed to the virtual constructor, which then uses that information to determine which token to create Comment
The virtual constructor Shape(type) can only be declared inside the class; it cannot be defined
until after all the derived classes have been declared However, the default constructor can be
defined inside class Shape, but it should be made protected so temporary Shape objects
cannot be created This default constructor is only called by the constructors of derived-class objects You are forced to explicitly create a default constructor because the compiler will create
one for you automatically only if there are no constructors defined Because you must define
Shape(type), you must also define Shape( ) Comment
The default constructor in this scheme has at least one important chore—it must set the value of
the s pointer to zero This may sound strange at first, but remember that the default constructor
will be called as part of the construction of the actual object—in Coplien’s terms, the “letter,” not
the “envelope.” However, the “letter” is derived from the “envelope,” so it also inherits the data
member s In the “envelope,” s is important because it points to the actual object, but in the
“letter,” s is simply excess baggage Even excess baggage should be initialized, however, and if s is
not set to zero by the default constructor called for the “letter,” bad things happen (as you’ll see later) Comment
The virtual constructor takes as its argument information that completely determines the type of the object Notice, though, that this type information isn’t read and acted upon until runtime,
Trang 10whereas normally the compiler must know the exact type at compile time (one other reason this system effectively imitates virtual constructors) Comment
The virtual constructor uses its argument to select the actual (“letter”) object to construct, which
is then assigned to the pointer inside the “envelope.” At that point, the construction of the “letter” has been completed, so any virtual calls will be properly directed Comment
As an example, consider the call to draw( ) inside the virtual constructor If you trace this call (either by hand or with a debugger), you can see that it starts in the draw( ) function in the base class, Shape This function calls draw( ) for the “envelope” s pointer to its “letter.” All types derived from Shape share the same interface, so this virtual call is properly executed, even
though it seems to be in the constructor (Actually, the constructor for the “letter” has already completed.) As long as all virtual calls in the base class simply make calls to identical virtual functions through the pointer to the “letter,” the system operates properly Comment
To understand how it works, consider the code in main( ) To fill the vector shapes, “virtual constructor” calls are made to Shape Ordinarily in a situation like this, you would call the
constructor for the actual type, and the VPTR for that type would be installed in the object Here,
however, the VPTR used in each case is the one for Shape, not the one for the specific Circle, Square, or Triangle Comment
In the for loop where the draw( ) and erase( ) functions are called for each Shape, the virtual function call resolves, through the VPTR, to the corresponding type However, this is Shape in each case In fact, you might wonder why draw( ) and erase( ) were made virtual at all The reason shows up in the next step: the base-class version of draw( ) makes a call, through the
“letter” pointer s, to the virtual function draw( ) for the “letter.” This time the call resolves to the actual type of the object, not just the base class Shape Thus, the runtime cost of using virtual
constructors is one more virtual call every time you make a virtual function call Comment
To create any function that is overridden, such as draw( ), erase( ), or test( ), you must proxy all calls to the s pointer in the base class implementation, as shown earlier This is because, when the call is made, the call to the envelope’s member function will resolve as being to Shape, and not to a derived type of Shape Only when you make the proxy call to s will the virtual behavior take place In main( ), you can see that everything works correctly, even when calls are made
inside constructors and destructors Comment
Destructor operation
The activities of destruction in this scheme are also tricky To understand, let’s verbally walk
through what happens when you call delete for a pointer to a Shape object—specifically, a Square—created on the heap (This is more complicated than an object created on the stack.) This will be a delete through the polymorphic interface, as in the statement delete shapes[i] in main( ) Comment
The type of the pointer shapes[i] is of the base class Shape, so the compiler makes the call through Shape Normally, you might say that it’s a virtual call, so Square’s destructor will be called But with the virtual constructor scheme, the compiler is creating actual Shape objects, even though the constructor initializes the letter pointer to a specific type of Shape The virtual
mechanism is used, but the VPTR inside the Shape object is Shape’s VPTR, not Square’s This
resolves to Shape’s destructor, which calls delete for the letter pointer s, which actually points to
a Square object This is again a virtual call, but this time it resolves to Square’s destructor
Comment
With a destructor, however, C++ guarantees, via the compiler, that all destructors in the hierarchy
are called Square’s destructor is called first, followed by any intermediate destructors, in order,
until finally the base-class destructor is called This base-class destructor has code that says
Trang 11delete s When this destructor was called originally, it was for the “envelope” s, but now it’s for the “letter” s, which is there because the “letter” was inherited from the “envelope,” and not
because it contains anything So this call to delete should do nothing Comment
The solution to the problem is to make the “letter” s pointer zero Then when the “letter” class destructor is called, you get delete 0, which by definition does nothing Because the default
base-constructor is protected, it will be called only during the construction of a “letter,” so that’s the
only situation in which s is set to zero Comment
Your most common tool for hiding construction will probably be ordinary Factory Methods rather than the more complex approaches The idea of adding new types with minimal effect on the rest
of the system will be further explored later in this chapter Comment
Observer
The Observer pattern solves a fairly common problem: what if a group of objects needs to update themselves when some other object changes state? This can be seen in the “model-view” aspect of Smalltalk’s MVC (model-view-controller) or the almost-equivalent “Document-View
Architecture.” Suppose that you have some data (the “document”) and more than one view, say a plot and a textual view When you change the data, the two views must know to update
themselves, and that’s what the observer facilitates Comment
Two types of objects are used to implement the observer pattern in the following code The
Observable class keeps track of everybody who wants to be informed when a change happens The Observable class calls the notifyObservers( ) member function for each observer on the list The notifyObservers( ) member function is part of the base class Observable Comment
There are actually two “things that change” in the observer pattern: the quantity of observing objects and the way an update occurs That is, the observer pattern allows you to modify both of these without affecting the surrounding code Comment
You can implement the observer pattern in a number of ways, but the code shown here will create
a framework from which you can build your own observer code, following the example First, this interface describes what an observer looks like: Comment
// Called by the observed object, whenever
// the observed object is changed:
argument you want to pass during an update If you want, you can simply pass the extra argument
as a void* You’ll have to downcast in either case Comment
Trang 12The Observer type is an “interface” class that only has one member function, update( ) This
function is called by the object that’s being observed, when that object decides it’s time to update
all its observers The arguments are optional; you could have an update( ) with no arguments,
and that would still fit the observer pattern However this is more general—it allows the observed
object to pass the object that caused the update (since an Observer may be registered with more
than one observed object) and any extra information if that’s helpful, rather than forcing the
Observer object to hunt around to see who is updating and to fetch any other information it
virtual void setChanged() { changed = true; }
virtual void clearChanged(){ changed = false; }
virtual bool hasChanged() { return changed; }
// If this object has changed, notify all
Again, the design here is more elaborate than is necessary; as long as there’s a way to register an
Observer with an Observable and a way for the Observable to update its Observers, the set
of member functions doesn’t matter However, this design is intended to be reusable (It was lifted from the design used in the Java standard library.) Comment
The Observable object has a flag to indicate whether it’s been changed In a simpler design,
there would be no flag; if something happened, everyone would be notified Notice, however, that
[123]
Trang 13the control of the flag’s state is protected so that only an inheritor can decide what constitutes a change, and not the end user of the resulting derived Observer class Comment
The collection of Observer objects is kept in a set<Observer*> to prevent duplicates; the set insert( ), erase( ), clear( ), and size( ) functions are exposed to allow Observers to be added
and removed at any time, thus providing runtime flexibility Comment
Most of the work is done in notifyObservers( ) If the changed flag has not been set, this does nothing Otherwise, it first clears the changed flag so that repeated calls to notifyObservers( ) won’t waste time This is done before notifying the observers in case the calls to update( ) do anything that causes a change back to this Observable object It then moves through the set and calls back to the update( ) member function of each Observer Comment
At first it may appear that you can use an ordinary Observable object to manage the updates
But this doesn’t work; to get an effect, you must derive from Observable and somewhere in your
derived-class code call setChanged( ) This is the member function that sets the “changed” flag, which means that when you call notifyObservers( ) all the observers will, in fact, get notified
Now we encounter a dilemma Objects that are being observed may have more than one such item
of interest For example, if you’re dealing with a GUI item—a button, say—the items of interest might be the mouse clicked the button, the mouse moved over the button, and (for some reason) the button changed its color So we’d like to be able to report all these events to different
observers, each of which is interested in a different type of event Comment
The problem is that we would normally reach for multiple inheritance in such a situation: “I’ll
inherit from Observable to deal with mouse clicks, and I’ll … er … inherit from Observable to
deal with mouse-overs, and, well, … hmm, that doesn’t work.” Comment
The “inner class” idiom
Here’s a situation in which we do actually need to (in effect) upcast to more than one type, but in
this case we need to provide several different implementations of the same base type The solution
is something we’ve lifted from Java, which takes C++’s nested class one step further Java has a
built-in feature called an inner class, which is like a nested class in C++, but it has access to the
nonstatic data of its containing class by implicitly using the “this” pointer of the class object it was created within Comment
To implement the inner class idiom in C++, we must obtain and use a pointer to the containing object explicitly Here’s an example:
Trang 14friend class Outer::Inner1;
class Inner1 : public Poingable {
friend class Outer::Inner2;
class Inner2 : public Bingable {
// Return reference to interfaces
// implemented by the inner classes:
operator Poingable&() { return inner1; }
operator Bingable&() { return inner2; }
};
int main() {
Outer x("Ping Pong");
// Like upcasting to multiple base types!:
perform object destruction via the interface Comment
The Outer constructor contains some private data (name), and it wants to provide both a Poingable interface and a Bingable interface so it can be used with callPoing( ) and callBing
Trang 15( ) Of course, in this situation we could simply use multiple inheritance This example is just
intended to show the simplest syntax for the idiom; you’ll see a real use shortly To provide a
Poingable object without deriving Outer from Poingable, the inner class idiom is used First, the declaration class Inner says that, somewhere, there is a nested class of this name This allows the friend declaration for the class, which follows Finally, now that the nested class has been granted access to all the private elements of Outer, the class can be defined Notice that it keeps a pointer to the Outer which created it, and this pointer must be initialized in the
constructor Finally, the poing( ) function from Poingable is implemented The same process occurs for the second inner class which implements Bingable Each inner class has a single private instance created, which is initialized in the Outer constructor By creating the member
objects and returning references to them, issues of object lifetime are eliminated Comment
Notice that both inner class definitions are private, and in fact the client code doesn’t have any access to details of the implementation, since the two access functions operator Poingable&( ) and operator Bingable&( ) only return a reference to the upcast interface, not to the object that implements it In fact, since the two inner classes are private, the client code cannot even
downcast to the implementation classes, thus providing complete isolation between interface and implementation Comment
Just to push a point, we’ve taken the extra liberty here of defining the automatic type conversion
operators operator Poingable&( ) and operator Bingable&( ) In main( ), you can see that these actually allow a syntax that looks like Outer multiply inherits from Poingable and
Bingable The difference is that the casts in this case are one way You can get the effect of an upcast to Poingable or Bingable, but you cannot downcast back to an Outer In the following example of observer, you’ll see the more typical approach: you provide access to the inner class
objects using ordinary member functions, not automatic type conversion operations Comment
The observer example
Armed with the Observer and Observable header files and the inner class idiom, we can look at
an example of the Observer pattern:
Trang 16friend class Flower::OpenNotifier;
class OpenNotifier : public Observable { Flower* parent;
bool alreadyOpen;
public:
OpenNotifier(Flower* f) : parent(f), alreadyOpen(false) {}
void notifyObservers(Argument* arg = 0) { if(parent->isOpen && !alreadyOpen) { setChanged();
friend class Flower::CloseNotifier;
class CloseNotifier : public Observable { Flower* parent;
bool alreadyClosed;
public:
CloseNotifier(Flower* f) : parent(f), alreadyClosed(false) {}
void notifyObservers(Argument* arg = 0) { if(!parent->isOpen && !alreadyClosed) { setChanged();
friend class Bee::OpenObserver;
class OpenObserver : public Observer { Bee* parent;
public:
OpenObserver(Bee* b) : parent(b) {} void update(Observable*, Argument *) { cout << "Bee " << parent->name
friend class Bee::CloseObserver;
class CloseObserver : public Observer { Bee* parent;
public:
CloseObserver(Bee* b) : parent(b) {} void update(Observable*, Argument *) { cout << "Bee " << parent->name
<< "'s bed time!\n";
}
} closeObsrv;
Trang 17cout << "Hummingbird " << parent->name << "'s breakfast time!\n";
cout << "Hummingbird " << parent->name << "'s bed time!\n";
f.openNotifier.deleteObserver(hb.openObserver()); // Something changes that interests observers: f.open();
f.open(); // It's already open, no change
// Bee A doesn't want to go to bed:
Trang 18InnerClassIdiom.cpp, the Observable descendants are public This is because some of their
member functions must be available to the client programmer There’s nothing that says that an
inner class must be private; in InnerClassIdiom.cpp we were simply following the design guideline “make things as private as possible.” You could make the classes private and expose the appropriate member functions by proxy in Flower, but it wouldn’t gain much Comment
The inner class idiom also comes in handy to define more than one kind of Observer, in Bee and Hummingbird, since both those classes may want to independently observe Flower openings
and closings Notice how the inner class idiom provides something that has most of the benefits of inheritance (the ability to access the private data in the outer class, for example) Comment
In main( ), you can see one of the primary benefits of the Observer pattern: the ability to change behavior at runtime by dynamically registering and unregistering Observers with Observables
of either a or b, how can you get them to interact properly? Comment
The answer starts with something you probably don’t think about: C++ performs only single dispatching That is, if you are performing an operation on more than one object whose type is unknown, C++ can invoke the dynamic binding mechanism on only one of those types This doesn’t solve the problem, so you end up detecting some types manually and effectively producing your own dynamic binding behavior Comment
The solution is called multiple dispatching Remember that polymorphism can occur only via
member function calls, so if you want double dispatching to occur, there must be two member function calls: the first to determine the first unknown type, and the second to determine the second unknown type With multiple dispatching, you must have a virtual call for each of the types Generally, you’ll set up a configuration such that a single member function call produces more than one dynamic member function call and thus services more than one type in the
process To get this effect, you need to work with more than one virtual function: you’ll need a virtual function call for each dispatch The virtual functions in the following example are called
compete( ) and eval( ) and are both members of the same type (In this case, there will be only
two dispatches, which is referred to as double dispatching.) If you are working with two different
type hierarchies that are interacting, you’ll need a virtual call in each hierarchy Comment
Here’s an example of multiple dispatching:
Trang 19case win: return os << "win";
case lose: return os << "lose";
case draw: return os << "draw";
Trang 20class Scissors : public Item {
}
};
int main() {
Trang 21Multiple dispatching with Visitor
The assumption is that you have a primary class hierarchy that is fixed; perhaps it’s from another vendor and you can’t make changes to that hierarchy However, you’d like to add new
polymorphic member functions to that hierarchy, which means that normally you’d have to add something to the base class interface So the dilemma is that you need to add member functions to the base class, but you can’t touch the base class How do you get around this? Comment
The design pattern that solves this kind of problem is called a “visitor” (the final one in Design
The Visitor pattern allows you to extend the interface of the primary type by creating a separate
class hierarchy of type Visitor to “virtualize” the operations performed on the primary type The objects of the primary type simply “accept” the visitor and then call the visitor’s dynamically-
bound member function Comment
virtual void visit(Gladiolus* f) = 0;
virtual void visit(Renuculus* f) = 0;
virtual void visit(Chrysanthemum* f) = 0;
Trang 22virtual void visit(Gladiolus*) {
cout << "Bee and Gladiolus\n";
}
virtual void visit(Renuculus*) {
cout << "Bee and Renuculus\n";
}
virtual void visit(Chrysanthemum*) { cout << "Bee and Chrysanthemum\n"; }
case 0: return new Gladiolus;
case 1: return new Renuculus;
case 2: return new Chrysanthemum; }
Trang 23vector<Flower*>::iterator it;
// It's almost as if I added a virtual function
// to produce a Flower string representation:
service that stores and retrieves data from a configuration file
number of its own objects Assume the objects are database connections and you only have
a license to use a fixed quantity of these at any one time
without the extra arguments in Observer.h and the member functions in Observable.h
Just create the bare minimum in the two classes, and then demonstrate your design by
creating one Observable and many Observers and cause the Observable to update the Observers.
inner class idiom
Method.
of shapes (for example, one particular type of factory object creates “thick shapes,” another creates “thin shapes,” but each factory object can create all the shapes: circles, squares, triangles, and so on)
engineers), Elf (for marketers), and Troll (for managers) Now create a class called
Project that creates the different inhabitants and causes them to interact( ) with each
other using multiple dispatching
Inhabitant can randomly produce a Weapon using getWeapon( ): a Dwarf uses Jargon or Play, an Elf uses InventFeature or SellImaginaryProduct, and a Troll uses Edict and Schedule You decide which weapons “win” and “lose” in each interaction (as in PaperScissorsRock.cpp) Add a battle( ) member function to Project that takes two Inhabitants and matches them against each other Now create a meeting( ) member function for Project that creates groups of Dwarf, Elf, and Manager and battles the
groups against each other until only members of one group are left standing These are the
“winners.”
11: Concurrency
Objects provide a way to divide a program into independent sections Often, you also need to partition a program into separate,
Trang 24independently running subtasks.
Using multithreading, each of these independent subtasks is driven by a thread of execution, and
you program as if each thread runs by itself and has the CPU to itself An underlying mechanism
is actually dividing up the CPU time for you, but in general, you don’t have to think about it, which helps to simplify programming with multiple threads
A process is a self-contained program running with its own address space A multitasking
operating system can run more than one process (program) at a time, while making it look as if each one is chugging along on its own, by periodically switching the CPU from one task to
another A thread is a single sequential flow of control within a process A single process can thus
have multiple concurrently executing threads
There are many possible uses for multithreading, but you’ll most often want to use it when you have some part of your program tied to a particular event or resource To keep from holding up the rest of your program, you create a thread associated with that event or resource and let it run independently of the main program
Concurrent programming is like stepping into an entirely new world and learning a new
programming language, or at least a new set of language concepts With the appearance of thread support in most microcomputer operating systems, extensions for threads have also been
appearing in programming languages or libraries In all cases, thread programming:
understand a common tongue
Understanding concurrent programming is on the same order of difficulty as understanding polymorphism If you apply some effort, you can fathom the basic mechanism, but it generally takes deep study and understanding to develop a true grasp of the subject The goal of this chapter
is to give you a solid foundation in the basics of concurrency so that you can understand the concepts and write reasonable multithreaded programs Be aware that you can easily become overconfident If you are writing anything complex, you will need to study dedicated books on the topic
Motivation
One of the most compelling reasons for using concurrency is to produce a responsive user
interface Consider a program that performs some CPU-intensive operation and thus ends up ignoring user input and being unresponsive The program needs to continue performing its operations, and at the same time it needs to return control to the user interface so that the
program can respond to the user If you have a “quit” button, you don’t want to be forced to poll it
in every piece of code you write in your program (This would couple your quit button across the program and be a maintenance headache.) Yet you want the quit button to be responsive, as if you
were checking it regularly.
A conventional function cannot continue performing its operations and at the same time return control to the rest of the program In fact, this sounds like an impossibility, as if the CPU must be
in two places at once, but this is precisely the illusion that concurrency provides
You can also use concurrency to optimize throughput For example, you might be able to do important work while you’re stuck waiting for input to arrive on an I/O port Without threading, the only reasonable solution is to poll the I/O port, which is awkward and can be difficult
If you have a multiprocessor machine, multiple threads can be distributed across multiple
Trang 25processors, which can dramatically improve throughput This is often the case with powerful multiprocessor web servers, which can distribute large numbers of user requests across CPUs in a program that allocates one thread per request.
One thing to keep in mind is that a program that has many threads must be able to run on a single-CPU machine Therefore, it must also be possible to write the same program without using any threads However, multithreading provides an important organizational benefit: The design
of your program can be greatly simplified Some types of problems, such as simulation—a video game, for example—are difficult to solve without support for concurrency
The threading model is a programming convenience to simplify juggling several operations at the same time within a single program: The CPU will pop around and give each thread some of its time Each thread has the consciousness of constantly having the CPU to itself, but the CPU’s time
is actually sliced among all the threads The exception is a program that is running on multiple CPU But one of the great things about threading is that you are abstracted away from this layer,
so your code does not need to know whether it is actually running on a single CPU or many Thus, using threads is a way to create transparently scalable programs—if a program is running too slowly, you can easily speed it up by adding CPUs to your computer Multitasking and
multithreading tend to be the most reasonable ways to utilize multiprocessor systems
Threading can reduce computing efficiency somewhat in single-CPU machines, but the net
improvement in program design, resource balancing, and user convenience is often quite
valuable In general, threads enable you to create a more loosely coupled design; otherwise, parts
of your code would be forced to pay explicit attention to tasks that would normally be handled by threads
Concurrency in C++
When the C++ standards committee was creating the initial C++ standard, a concurrency
mechanism was explicitly excluded, because C didn’t have one and also because there were a number of competing approaches to implementing concurrency It seemed too much of a
constraint to force programmers to use only one of these
The alternative turned out to be worse, however To use concurrency, you had to find and learn a library and deal with its idiosyncrasies and the uncertainties of working with a particular vendor
In addition, there was no guarantee that such a library would work on different compilers or across different platforms Also, since concurrency was not part of the standard language, it was more difficult to find C++ programmers that also understood concurrent programming
Another influence may have been the Java language, which included concurrency in the core language Although multithreading is still complicated, Java programmers tend to start learning and using it from the beginning
The C++ standards committee is seriously considering the addition of concurrency support to the next iteration of C++, but at the time of this writing it is unclear what the library will look like Therefore, we decided to use the ZThread library as the basis for this chapter We preferred the
design, and it is open-source and freely available at http://zthread.sourceforge.net Eric Crahen
of IBM, the author of the ZThread library, was instrumental in creating this chapter
This chapter uses only a subset of the ZThread library, in order to convey the fundamental ideas of threading The ZThread library contains significantly more sophisticated thread support than is shown here, and you should study that library further in order to fully understand its capabilities
Installing ZThreads
Please note that the ZThread library is an independent project and is not supported by the authors
[125]
Trang 26of this book; we are simply using the library in this chapter and cannot provide technical support for installation issues See the ZThread web site for installation support and error reports.
The ZThread library is distributed as source code After downloading it from the ZThread web site, you must first compile the library, and then configure your project to use the library
The preferred method for compiling ZThreads for most flavors of UNIX (Linux, SunOS, Cygwin,
etc.) is to use the configure script After unpacking the files (using tar), simply execute:
./configure && make install
from the main directory of the ZThreads archive to compile and install a copy of the library in
the /usr/local directory You can customize a number of options when using this script, including
the locations of files For details, use this command:
./configure –help
The ZThreads code is structured to simplify compilation for other platforms and compilers (such
as Borland, Microsoft, and Metrowerks) To do this, create a new project and add all the cxx files
in the src directory of the ZThreads archive to the list of files to be compiled Also, be sure to include the include directory of the archive in the header search path for your project The exact
details will vary from compiler to compiler so you’ll need to be somewhat familiar with your toolset to be able to use this option
Once the compilation has succeeded, the next step is to create a project that uses the newly compiled library First, let the compiler know where the headers are located so that your
#include statements will work properly Typically, you will add an option such as the following
to your project:
-I/path/to/installation/include
If you used the configure script, the installation path will be whatever you selected for the prefix (which defaults to /usr/local) If you used one of the project files in the build directory, the
installation path would simply be the path to the main directory of the ZThreads archive
Next, you’ll need to add an option to your project that will let the linker know where to find the library If you used the configure script, this will look like:
-L/path/to/installation/lib –lZThread
If you used one of the project files provided, this will look like:
-L/path/to/installation/Debug ZThread.lib
Again, if you used the configure script, the installation path will be whatever you selected for the
prefix If you used a provided project file, the path will be the path to the main directory of the ZThreads archive
Note that if you’re using Linux, or if you are using Cygwin (www.cygwin.com) under Windows, you may not need to modify your include or library path; the installation process and defaults will often take care of everything for you
Under Linux, you will probably need to add the following to your bashrc so that the runtime system can find the shared library file LibZThread-x.x.so.O when it executes the programs in
this chapter:
export LD_LIBRARY_PATH=/usr/local/lib:${LD_LIBRARY_PATH}