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.
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
// 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 |