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
This example also illustrates
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."
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.
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
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.
Coplien[122] 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:
//: C10:VirtualConstructor.cpp
#include
#include
#include
#include
using namespace std;
class Shape {
Shape* s;
// Prevent copy-construction & operator=
Shape(Shape&);
Shape operator=(Shape&);
protected:
Shape() { s = 0; };
public:
virtual void draw() { s->draw(); }
virtual void erase() { s->erase(); }
virtual void test() { s->test(); };
virtual ~Shape() {
cout << "~Shape\n";
if(s) {
cout << "Making virtual call: ";
s->erase(); // Virtual call
}
cout << "delete s: ";
delete s; // The polymorphic deletion
}
class BadShapeCreation : public exception {
string reason;
public:
BadShapeCreation(string type) {
reason = "Cannot create type " + type;
}
~BadShapeCreation() throw() {}
const char *what() const throw() {
return reason.c_str();
}
};
Shape(string type) throw(BadShapeCreation);
};
class Circle : public Shape {
Circle(Circle&);
Circle operator=(Circle&);
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(Square&);
Square operator=(Square&);
Square() {}
friend class Shape;
public:
void draw() { cout << "Square::draw\n"; }
void erase() { cout << "Square::erase\n"; }
void test() { draw(); }
~Square() { cout << "Square::~Square\n"; }
};
Shape::Shape(string type)
throw(Shape::BadShapeCreation) {
if(type == "Circle")
s = new Circle;