We offer an example that presents the envelope and letter idiom in greater depth. For this discussion, we return to an example discussed in the earlier discussion on polymorphism. We use registration and prototypes to decouple creation from the base class.
Our new class has a number of advantages:
First, let's see how the new classes are used:
#include <shaperep.h> #include <iostream> #include <list> using Shapes::Shape; int main() { std::string request; std::list<Shape> s; Shape tmp; while (request != "q" ) try { std::cout << "Enter the name of a shape (q to quit)" << std::endl; std::cin >> request; if ( request != "q" ) { tmp = Shapes::God->make(request); s.push_back(tmp); } } catch (const Shapes::Exception& e) { std::cout << request << " is unsupported " << std::endl; } std::list<Shape>::iterator it; for ( it = s.begin(); it != s.end(); ++it ) (*it)->draw(); return 0; }
Now let's explain what's happening here:
I'm sorry if I labored the point about automatic storage, but it's an important one, as the idea of calling methods with -> on objects that don't require manual cleanup may seem a little odd.
Without further ado, we move on to the ...
This code also requires some explanation. We discuss shape.h The implementation shape.cc is pretty straightforward once the interface has been decided.
The only comment I have about shape.cc is the observation that one needs to test for self assignment (or in particular, to test that Rep != e.Rep, which is equivalent in our case) We could bypass the problem entirely by cloning before we delete, but there's another reason to do the check -- we can optimise an unneccessary allocation/deletion.
#ifndef SHAPE_H #define SHAPE_H #include <string> #include <list> // Some of our classnames are very common (like Exception). So we'd better // have a namespace namespace Shapes { // Exception class for this application. class Exception { public: Exception(const char* s = "" ) : msg(s){} Exception(std::string s = "" ) : msg(s){} std::string message () const { return msg; } private: std::string msg; }; // Dummy class to distinguish registration constructor from other constructors struct Registration { Registration(std::string name) : name(name){} std::string name; }; class ShapeRep; // Envelope class class Shape { public: Shape(); Shape(const Shape& e) ; Shape& operator= (const Shape& e); Shape(ShapeRep& r); ~Shape(); ShapeRep* operator->(); private: ShapeRep* Rep; }; std::list<Shape>& PrototypeList(); } // namespace #endif
#include <shape.h> #include <shaperep.h> using namespace Shapes; Shape::Shape() : Rep(0){} Shape::Shape(const Shape& e) { if (e.Rep) Rep = e.Rep->clone(); else Rep = 0; } Shape::Shape(ShapeRep& r) { Rep = r.clone(); } Shape& Shape::operator= (const Shape& e) { if ( &e != this ) { delete Rep; Rep = e.Rep->clone(); } return *this; } Shape::~Shape() { delete Rep; } ShapeRep* Shape::operator->() { return Rep; } std::list<Shape>& Shapes::PrototypeList() { static std::list<Shape> x; return x; }
ShapeRep serves as the base letter class. The letter class is where most of the intelligence of the whole scheme lies, the envelope typically does little besides provide automatic management for the letter. This class is similar to the base we offered in the previous discussion on registration. Some key points:
Now the easy part. We only need implement isMine() and draw(). The other methods are essentially boilerplate code.
#ifndef SHAPEREP_H #define SHAPEREP_H #include <iostream> #include <list> #include <string> #include <shape.h> // This class cannot have pure virtual methods because we need a prototype namespace Shapes { class ShapeRep { public: ShapeRep(const Registration&) { PrototypeList().push_back(Shape(*this)); } virtual void draw() const {} virtual ~ShapeRep(); virtual ShapeRep* clone() const { return new ShapeRep; } virtual Shape make(std::string request_code) const; virtual bool isMine (std::string request_code); protected: ShapeRep(){} ShapeRep(const ShapeRep&){} ShapeRep& operator=(const ShapeRep&) { return *this; } }; extern Shape God; } //namespace #endif
#include <iostream> #include <list> #include <string> #include <shape.h> #include <shaperep.h> using namespace Shapes; ShapeRep::~ShapeRep(){} Shape ShapeRep::make (std::string request_code) const { std::list<Shape>::iterator it = PrototypeList().begin(); for ( ; it != PrototypeList().end(); ++it) { if ( (*it)->isMine ( request_code ) ) return (*it)->make( request_code ); } throw Exception ( std::string("make request failed for unknown shape ") + request_code ); } bool ShapeRep::isMine (std::string request_code) { return false; } namespace { ShapeRep& Prototype() { static ShapeRep t(Registration("Shape")); return t; } } Shape Shapes::God ( Prototype() );
#ifndef SQUARE_H #define SQUARE_H #include <shaperep.h> #include <string> namespace Shapes { class Square: public ShapeRep { public: Square(){} Square(const Registration&) { PrototypeList().push_back(Shape(*this)); } virtual ShapeRep* clone () const; virtual Shape make (std::string request_code) const; virtual void draw ()const; virtual bool isMine(std::string request_code) { return request_code == "square"; } virtual ~Square (); }; extern Shape Shapes::SquarePrototype; } //namespace #endif
#include <square.h> using namespace Shapes; ShapeRep* Square::clone () const { return new Square; } Shape Square::make (std::string request_code) const { Square* ret = new Square; return Shape(*ret); } void Square::draw () const { std::cout << " ______________ \n" << " |/ | \n" << " |// ==== | \n" << " |/// ====| \n" << " |///// | \n" << " |//=== | \n" << " |////======== | \n" << " |/========// | \n" << " |//==/-- | \n" << " L______________J \n" << std:: endl; } Square::~Square() {} namespace { Square& Prototype() { static Square t(Registration("triangle")); return t; } } Shape Shapes::SquarePrototype ( Prototype() );
#ifndef TRIANGLE_H #define TRIANGLE_H #include <shaperep.h> #include <string> namespace Shapes { class Triangle : public ShapeRep { public: Triangle(){} Triangle(const Registration&) { PrototypeList().push_back(Shape(*this)); } virtual ShapeRep* clone ()const ; virtual Shape make (std::string request_code)const ; virtual void draw ()const ; virtual bool isMine(std::string request_code) { return request_code == "triangle";} virtual ~Triangle(); }; extern Shape Shapes::TrianglePrototype; } // namespace #endif
#include <triangle.h> #include <shape.h> using namespace Shapes; ShapeRep* Triangle::clone () const { return new Triangle; } Shape Triangle::make (std::string request_code) const { Triangle* ret = new Triangle; return Shape(*ret); } void Triangle::draw () const { std::cout << " |\\ \n" << " |+\\ \n" << " |= \\ \n" << " |= =\\ \n" << " |== =\\ \n" << " |== ==\\ \n" << " |== ===\\ \n" << " |== =\\ \n" << " |= +++==\\ \n" << " |=+ +====\\ \n" << " |== + =\\ \n" << " |=+ ==\\ \n" << " L____________\\ \n" << std:: endl; } Triangle::~Triangle() { } namespace { Triangle& Prototype() { static Triangle t(Registration("triangle")); return t; } } Shape Shapes::TrianglePrototype ( Prototype() );
#ifndef CIRCLE_H #define CIRCLE_H #include <shaperep.h> #include <string> namespace Shapes { class Circle : public ShapeRep { public: Circle(){} Circle(const Registration&) { PrototypeList().push_back(Shape(*this)); } virtual ShapeRep* clone () const; virtual Shape make (std::string request_code) const; virtual void draw () const; virtual bool isMine(std::string request_code) { return request_code == "circle"; } virtual ~Circle(); }; extern Shape Shapes::CirclePrototype; }//namespace #endif
#include <shape.h> #include <circle.h> #include <string> using namespace Shapes; ShapeRep* Circle::clone () const { return new Circle; } Shape Circle::make (std::string request_code) const { Circle* ret = new Circle; return Shape(*ret); } void Circle::draw () const { std::cout << " **** \n" << " * * \n" << " * * \n" << " * * \n" << " * * \n" << " * * \n" << " * * \n" << " * * \n" << " * * \n" << " **** \n" << std:: endl; } Circle::~Circle(){} namespace { Circle& Prototype() { static Circle t(Registration("triangle")); return t; } } Shape Shapes::CirclePrototype ( Prototype() );
Advanced C++ Programming Styles and Idioms, James Coplien
This book covers this topic in more depth, including additional material
regarding runtime loading of classes, a general form including automatic
memory management (the book discusses reference counting as well as
garbage collection, which is not the same as reference counting.)
Design Patterns, Eric Gamma et al.
This book covers a lot of powerful OO programming techniques.