MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

A simpler alternative

This “virtual constructor” scheme is unnecessarily complicated for most needs. If you can restrict yourself to objects created on the heap, things can be made a lot simpler. All you really want is some function (which I’ll call an object-maker function) into which you can throw a bunch of information and it will produce the right object. This function doesn’t have to be a constructor if it’s OK to produce a pointer to the base type rather than an object itself. Here’s the envelope-and-letter example redesigned using an object-maker function:

//: C:ShapeV2.cpp
// Alternative to ShapeV.cpp
#include <iostream>
#include <vector>
#include "../purge.h"
using namespace std;

class Shape {
  Shape(Shape&); // No copy-construction
protected:
  Shape() {} // Prevent stack objects
  // But allow access to derived constructors
public:
  enum type { tCircle, tSquare, tTriangle };
  virtual void draw() = 0;
  virtual ~Shape() { cout << "~Shape\n"; }
  static Shape* make(type);
};

class Circle : public Shape {
  Circle(Circle&); // No copy-construction
  Circle operator=(Circle&); // No operator=
protected:
  Circle() {};
public:
  void draw() { cout << "Circle::draw\n"; }
  ~Circle() { cout << "~Circle\n"; }
  friend Shape* Shape::make(type t);
};

class Square : public Shape {
  Square(Square&); // No copy-construction
  Square operator=(Square&); // No operator=
protected:
  Square() {};
public:
  void draw() { cout << "Square::draw\n"; }
  ~Square() { cout << "~Square\n"; }
  friend Shape* Shape::make(type t);
};

class Triangle : public Shape {
  Triangle(Triangle&); // No copy-construction
  Triangle operator=(Triangle&); // Prevent
protected:
  Triangle() {};
public:
  void draw() { cout << "Triangle::draw\n"; }
  ~Triangle() { cout << "~Triangle\n"; }
  friend Shape* Shape::make(type t);
};

Shape* Shape::make(type t) {
  Shape* S;
  switch(t) {
    case tCircle: S = new Circle; break;
    case tSquare: S = new Square; break;
    case tTriangle: S = new Triangle; break;
  }
  S->draw(); // Virtual function call
  return S;
}

int main() {
  vector<Shape*> shapes;
  cout << "virtual constructor calls:" << endl;
  shapes.push_back(Shape::make(Shape::tCircle));
  shapes.push_back(Shape::make(Shape::tSquare));
  shapes.push_back(Shape::make(Shape::tTriangle));
  cout << "virtual function calls:\n";
  for(int i = 0; i < shapes.size(); i++)
    shapes[i]->draw();
  //!Circle c; // Error: can't create on stack
  purge(shapes);
} ///:~ 

You can see that everything’s a lot simpler, and you don’t have to worry about strange cases with an internal pointer. The only restrictions are that the enum in the base class must be changed every time you derive a new class (true also with the envelope-and-letter approach) and that objects must be created on the heap. This latter restriction is enforced by making all the constructors protected, so the user can’t create an object of the class, but the derived-class constructors can still access the base-class constructors during object creation. This is an excellent example of where the protected keyword is essential.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru