Ru-Brd |
Templates can also be used to implement polymorphism. However, they don't rely on the factoring of common behavior in base classes. Instead, the commonality is implicit in that the different " shapes " of an application must support operations using common syntax (that is, the relevant functions must have the same names ). Concrete classes are defined independently from each other (see Figure 14.2). The polymorphic power is then enabled when templates are instantiated with the concrete classes. Figure 14.2. Polymorphism implemented via templates
For example, the function myDraw() in the previous section void myDraw (GeoObj const& obj) // GeoObj is abstract base class { obj.draw(); } could conceivably be rewritten as follows : template <typename GeoObj> void myDraw (GeoObj const& obj) // GeoObj is template parameter { obj.draw(); } Comparing the two implementations of myDraw() , we may conclude that the main difference is the specification of GeoObj as a template parameter instead of a common base class. There are, however, more fundamental differences under the hood. For example, using dynamic polymorphism we had only one myDraw() function at run time, whereas with the template we have distinct functions, such as myDraw<Line>() and myDraw<Circle>() . We may attempt to recode the complete example of the previous section using static polymorphism. First, instead of a hierarchy of geometric classes, we have several individual geometric classes: // poly/statichier.hpp #include "coord.hpp" // concrete geometric object class Circle // - not derived from any class class Circle { public: void draw() const; Coord center_of_gravity() const; }; // concrete geometric object class Line // - not derived from any class class Line { public: void draw() const; Coord center_of_gravity() const; }; Now, the application of these classes looks as follows: // poly/staticpoly.cpp #include "statichier.hpp" #include <vector> // draw any GeoObj template <typename 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 template <typename GeoObj1, typename GeoObj2> Coord distance (GeoObj1 const& x1, GeoObj2 const& x2) { Coord c = x1.center_of_gravity() - x2.center_of_gravity(); return c.abs(); // return coordinates as absolute values } // draw homogeneous collection of GeoObj s template <typename GeoObj> 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<Line>(GeoObj&) => Line::draw() myDraw(c); // myDraw<Circle>(GeoObj&) => Circle::draw() distance(c1,c2); // distance<Circle,Circle>(GeoObj1&,GeoObj2&) distance(l,c); // distance<Line,Circle>(GeoObj1&,GeoObj2&) // std::vector<GeoObj*> coll; // ERROR: no inhomogeneous // collection possible std::vector<Line> coll; // OK: homogeneous collection possible coll.push_back(l); // insert line drawElems(coll); // draw all lines } As with myDraw() , GeoObj can no longer be used as a concrete parameter type for distance() . Instead, we provide for two template parameters GeoObj1 and GeoObj2 . By using two different template parameters, different combinations of geometric object types can be accepted for the distance computation: distance(l,c); // distance<Line,Circle>(GeoObj1&,GeoObj2&) However, heterogeneous collections can no longer be handled transparently . This is where the static part of static polymorphism imposes its constraint: All types must be determined at compile time. Instead, we can easily introduce different collections for different geometric object types. There is no longer a requirement that the collection be limited to pointers, which can have significant advantages in terms of performance and type safety. |
Ru-Brd |