14.1 Dynamic Polymorphism

Ru-Brd

Historically, C++ started with supporting polymorphism only through the use of inheritance combined with virtual functions. [2] The art of polymorphic design in this context consists of identifying a common set of capabilities among related object types and declaring them as virtual function interfaces in a common base class.

[2] Strictly speaking, macros can also be thought of as an early form of static polymorphism. However, they are left out of consideration because they are mostly orthogonal to the other language mechanisms.

The poster child for this design approach is an application that manages geometric shapes and allows them to be rendered in some way (for example, on a screen). In such an application we might identify a so-called abstract base class ( ABC ) GeoObj , which declares the common operations and properties applicable to geometric objects. Each concrete class for specific geometric objects then derives from GeoObj (see Figure 14.1):

Figure 14.1. Polymorphism implemented via inheritance

graphics/14fig01.gif

  // poly/dynahier.hpp  #include "coord.hpp"  // common abstract base class  GeoObj  for geometric objects  class GeoObj {    public:  // draw geometric object:  virtual void draw() const = 0;  // return center of gravity of geometric object:  virtual Coord center_of_gravity() const = 0;   };  // concrete geometric object class  Circle  // - derived from  GeoObj  class Circle : public GeoObj {    public:      virtual void draw() const;      virtual Coord center_of_gravity() const;   };  // concrete geometric object class  Line  // - derived from  GeoObj  class Line : public GeoObj {    public:      virtual void draw() const;      virtual Coord center_of_gravity() const;   };   

After creating concrete objects, client code can manipulate these objects through references or pointers to the base class, which enables the virtual function dispatch mechanism. Calling a virtual member function through a pointer or reference to a base class subobject results in an invocation of the appropriate member of the specific concrete object to which was referred.

In our example, the concrete code can be sketched as follows :

  // poly/dynapoly.cpp  #include "dynahier.hpp"  #include <vector>  // draw any  GeoObj  void myDraw (GeoObj const& obj)  {      obj.draw();  // call  draw()  according to type of object  }  // process distance of center of gravity between two  GeoObj  s  Coord distance (GeoObj const& x1, GeoObj const& x2)  {      Coord c = x1.center_of_gravity() - x2.center_of_gravity();      return c.abs();  // return coordinates as absolute values  }  // draw inhomogeneous collection of  GeoObj  s  void drawElems (std::vector<GeoObj*> const& elems)  {      for (unsigned i=0; i<elems.size(); ++i) {          elems[i]->draw();  // call  draw()  according to type of element  }  }  int main()  {      Line l;      Circle c, c1, c2;      myDraw(l);  //  myDraw(GeoObj&) => Line::draw()      myDraw(c);  //  myDraw(GeoObj&) => Circle::draw()      distance(c1,c2);  //  distance(GeoObj&,GeoObj&)      distance(l,c);  //  distance(GeoObj&,GeoObj&)      std::vector<GeoObj*> coll;  // inhomogeneous collection  coll.push_back(&l);  // insert line  coll.push_back(&c);  // insert circle  drawElems(coll);  // draw different kinds of  GeoObj  s  } 

The key polymorphic interface elements are the functions draw() and center_of_gravity() . Both are virtual member functions. Our example demonstrates their use in the functions mydraw() , distance() ,and drawElems() . The latter functions are expressed using the common base type GeoObj . As a consequence it cannot be determined at compile time which version of draw() or center_of_gravity() has to be used. However, at run time, the complete dynamic type of the objects for which the virtual functions are invoked is accessed to dispatch the function calls. Hence, depending on the actual type of a geometric object, the appropriate operation is done: If mydraw() is called for a Line object, the expression obj.draw() calls Line::draw() , whereas for a Circle object the function Circle::draw() is called. Similarly, with distance() the member functions center_of_gravity() appropriate for the argument objects are called.

Perhaps the most compelling feature of this dynamic polymorphism is the ability to handle heterogeneous collections of objects. drawElems() illustrates this concept: The simple expression

 elems[i]->draw() 

results in invocations of different member functions, depending on the type of the element being iterated over.

Ru-Brd


C++ Templates
C++ Templates: The Complete Guide
ISBN: 0201734842
EAN: 2147483647
Year: 2002
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net