Evaluation Issues in Generic Programming with Inheritance and Templates in C++ Emil Vassev, Joey Paquet Department of Computer Science and Software Engineering Concordia University Montreal, Quebec, H3G 1M8, Canada {i_vassev, paquet}@cse.concordia.ca Abstract Generic Programming is a concept and style of programming that increases the correctness and reusability of the source code. In this paper, we draw upon our experience and the experience of other researchers in this field to discuss the advantages and disadvantages of the two major models of generic programming - inheritance and templates. This article reveals some practical cases when the use of the two models is appropriate and when not recommended. Finally, the article concludes with an example of natural coexistence of the two models and demonstrates how they can complement each other. Keywords: Generic Programming, Templates, Inheritance, OOP, Class, Function 1. Introduction Writing generic code is related to making the code reusable and clear, hence it improves the programming efficiency. In C++, there are two major programming models that allow code reuse. Code reuse is possible through inheritance and templates. The main difference lies in how the reuse happens. Whereas in inheritance the code reuse happens across inherited classes, in templates we have a sort of macro substitution of the type name to generate code for that type name. Whereas the main policy provided by inheritance is that the class methods (also called member functions) should be written only once and propagated via inheritance, in programming with templates we follow the principle that algorithms should be written only once and not duplicated across different types [2]. 2. Generic Programming with Inheritance Inheritance is one of the cornerstones of OOP [3]. Inheritance allows classes to inherit class members from other classes. The advantage is that related classes could be clearly expressed, thus avoiding cluttering of programs through reuse of code [4]. By using inheritance, we can arrange classes having common features into a class hierarchy. Class hierarchy specifies a certain order among the similar classes - classes declared lower in the hierarchy inherit features (data and function members) from classes declared higher in the hierarchy. The general idea is to make classes being like another [3] through code reuse. For example, in [5] a star system has the features of a space system, due to the fact the first inherits the last. In addition, a star system maintains a vector of comets and asteroids, those not being maintained by the space system. Hence, the star system is like the space system, which is the base class (also called super class) and the star system is the derived class (also called sub class). Kinds of inheritance. As we have already specified, classes can inherit data and functions. Inheriting functions can be inheritance with
declarations of the function only, or inheritance with declarations and definitions of the functions. We refer to the first case as interface inheritance and the second case as implementation inheritance [1]. In addition, C++ provides three kinds of access inheritance public, protected and private, those restricting in some sense the access to the inherited members. Multiple inheritance is another controversial C++ feature, which allows a class to inherit more than one base class. This could result in complicated inheritance relationships. Ambiguity is one of these complications [6]. For example potential ambiguity is when we have two base classes A and B and both declare a public or protected data member age. Then if C inherits A and B it will have two data members called age, A::age and B::age. Then operation as: C* pc; pc->age; is illegal since it is ambiguous. We resolve the problem by explicit qualification: pc-> A::age. Concepts for generic programming. Generic programming with inheritance relies on two principle concepts: virtual functions and inheritance polymorphism. These two features of inheritance [3] provide a mechanism for providing generic functions that: are defined in the base class in a class hierarchy; will work on all the objects instantiated from derived classes; will have possibly a different behavior for different derived classes. In [1] Grogono defines the virtual functions as an inheritance feature that opens up the possibility of redefinition in derived classes, this leading to polymorphic behaviour. Also in [1] Grogono defines a particular set of circumstances for inheritance polymorphism defined simply as class-dependent behavior. Hence, we draw a conclusion that polymorphism is observed when a derived class object is accessed via a pointer or reference to a base class object and the base class defines a virtual function that is predefined in the derived class. For example in [5] the base class SpaceEntity defines a virtual functions show() that is predefined in all the derived classes. Hence, the polymorphism takes place when we call show() defined in a derived class via a pointer to SpaceEntity class. This technique is also called dynamic binding. In dynamic binding, the determination of which function should be called is not made until run-time [4], as opposed to static binding that happens at compile time [3]. Virtual functions also have the ability to be defined but not declared in a class pure virtual functions. Inheriting a pure virtual function results in interface inheritance (see kinds of inheritance). This introduces a special type of class called abstract class [1]. An abstract class is a class with at least one pure virtual function. Examples for generic programming with inheritance. In [3] Koenig et al. demonstrate how to obtain a value without knowing the object s type. They create a generic inheritance function that compares the grades of two Core objects, where Core is a base class defining the virtual grade() function: bool compare_grades(const Core& c1, const Core& c2) { return c1.grade() < c2.grade(); } This function works for all the instances of Core class as well as for all the instances of Grad class, which class inherits Core class. In the last case, we make polymorphic calls on the grade() function. In [1] Grogono presents an example of generic tree traversal routines one using generic programming with templates and one using generic programming with inheritance. For the inheritance solution to work, the classes must be derived from the same base class and the generic traversal function must have a parameter of type pointer-to-base-class [1]. In contrary, the template solution does not require any dependency between the classes, but the presence of the appropriate functions. Concerning the binding process the difference between the two solutions is that whereas the inheritance solution finds the appropriate derived class at run-time, the template solution matches the template parameters with the appropriate classes at compile time. Hence, each solution has its advantages since the early binding provides high efficiency and late binding
provides high flexibility. In [1] Grogono demonstrates when the inheritance solution is more efficient in case we need more run-time flexibility. Another example for generic programming with inheritance is the following code: Project* getbigger(project* pp1, Project* pp2) { Project *prj; }; if (*pp1 > *pp2) prj = pp1->clone(); else prj = pp2->clone(); return prj; This function compares two projects and creates a project clone of the bigger one. The Project class is the base class in the project hierarchy and the function getbigger() works on any class derived from Project class, since the parameters are pointers to the base class. Operator overloading. The example above reveals another aspect of generic programming operator overloading. In order to make our code generic it is often necessary to overload the standard C++ operators like <, >, ==, <<, >>, +, - etc. Both generic models inheritance and templates rely on operator overloading. Operator overloading is just like function overloading but with some restrictions. An operator function must be a member function or take at least one argument of a class (reference) and cannot have default arguments. Given the overall analysis above and the following examples, we conclude that generic programming with inheritance is appropriate when the generic objects are related through a hierarchical inheritance structure and these objects are open to polymorphic behavior. 3. Generic Programming with Templates Whereas the generic programming with inheritance is object oriented programming, the generic programming with templates is not. In fact, according to Joshua Juran in [2], these two programming models are by no means incompatible. Where does the advantage of using templates lie then? The answer is the nature of templates. Templates establish a style of generic programming that concentrates on the single, typesafe definition of an algorithm that works across any type [2] supporting the operations required by the algorithm. Kinds of templates. C++ provides two kinds of templates: class templates and function templates. Function templates allow writing generic functions that can be used with arbitrary types. For example, we may have searching and sorting routines which can be used with any arbitrary type. The Standard Template Library (STL) generic algorithms have been implemented as function templates, and the containers have been implemented as class templates. Following the rule that templates are not restricted to types, Grogono talks in [1] about non-type parameters template functions. For example, the following template function falls in this category: template<int Max> int randint() { }; and it is called like this: randint<6>(); Here the argument can be only an integer constant. There are some complications coming with the use of templates. In [1] Grogono reveals four possible complications as following: a) Compiling template code. With templates in order to compile, most of the C++ platforms require to put together the declaration and definition code in one header file. This is not a restriction of the concept of templates itself, but rather restriction of the C++ compiler. b) Template return types. There are some restrictions for template functions with return type template parameter. Such functions should have at least one parameter typed with the return type template parameter [1].
template <typename T> T f(const T& param) { }; c) Specialization and function overloading. With template specialization we redefine the default template implementation to handle a particular type in a different way. When we need some special definition for a general template we have a specialized version for this template (class or function), i.e. we specialize the template parameters (some or all of them) with special types. We can specialize class templates partially or fully. For example, the class template template <typename A,typenameB> class X { } can be fully specialized to arrive at: template <> class X<int, int> { } or can be partially specialized to arrive at: template <typename A,typenameB> class X<A, int> { } The function templates can only be fully specialized. The restriction is coming from the fact the function templates are C++ functions and when having the same name they overload. Hence, the function template template< typename A> void f( A, A ); and its partially specialized version template< typename A> void f( A, int ); are in fact two distinct function templates that overload. In [7] Sutter demonstrates some problems when specializing function templates. d) Default arguments. Templates allow default arguments: template <class A = Star, class B=Planet> class SpaceSystem However, definition like the following is not allowed: SpaceSystem s; We must use the brackets < > even though we intend to use the default values: SpaceSystem s < >; Where are the templates appropriate? One of the key strengths of generic programming with templates lies in the paradigm that an algorithm and a user-defined type, each developed without knowledge of the other, can nevertheless be combined [2]. The use of templates reduces the redundancy in the source code but does not affect the functionality of the code. In C++ without templates, in order to make our functions generic, we generate overloaded versions for the various data types. For example we will have multiple versions of the max(x,y) function, which function returns the larger of x and y. However, using templates provides much simpler way to do this. With templates, we define only one version of our function, i.e. we define the algorithm. The use of function templates is not appropriate in case we expect the compiler to convert the function arguments implicitly for us. The generic template functions prevent implicit conversions in some cases. In [1] Grogono demonstrates the problem arising with the max() function when the last is called as following: max (1, 3.4) This call is not possible because the compiler does not convert argument 1 from integer to double. In order to solve the problem we can explicitly cast one of the arguments: max (static_cast<double>(1), 3.4) Templates are very useful when implementing generic constructs like vectors, stacks, lists, queues etc, which constructs can be used with any arbitrary type. For example, we can easily implement a generic template class Queue<T> that stores any type of objects, integral types or even pointers. One
of the most famous STL template class is the vector class. In [5] the base class SpaceSystem maintains two sets one of gravity objects and one of satellites. Those sets are implemented as vectors of pointer objects: std::vector<a*> gravityobjects; std::vector<b*> satellites; Generally, the use of class templates is always appropriate when we want our class to be independent on the type of object it is intended to manage. In [3] Koenig and Moo demonstrate a generic template class called handle that encapsulates a handle behavior, i.e. it refers to an object and when we destroy the handle object it will destroy the associated object as well. The use of templates requires knowledge about the nature of the template arguments. It is extremely important to ensure that template arguments incorporate the behavior required by the template algorithm. For example, a class passed as a template parameter defines a function doit() if such function is called within the template body: template<class T> void executetask(t task) { task.doit(); }; C++ templates are a powerful tool for creating reusable code and they become even more powerful by relying on predefined library like the STL and Boost [1]. The STL provides containers, iterators, and algorithms. Boost includes libraries for threading and smart pointers. Smart pointers are reference counting pointer objects useful for deallocating memory. 4. Conclusion Generic programming with inheritance and generic programming with templates provide different approaches in making our code generic. A better approach or maybe better generic programming model will be the coexistence of these two models. For the development of Universum [5], we apply both generic programming models in an attempt to achieve a level of generic programming that is not possible if you stick only with one of them. The result is a coexistence of templates and inheritance where each working in its most appropriate area. We used templates for object containers and for the base SpaceSystem class. The SpaceSystem class is an abstract template class. It defines all the common functionality for all the possible space systems star system, planet system, galaxy and universe. By providing different template arguments, we specify different set of data and common functionality for each one, those preventing from redundancy coming with the creation of multiple base classes. By providing different definitions for the virtual member functions in the derived classes, we provide a polymorphic behavior for those functions. Therefore, we demonstrated the two generic programming models together in action, complementing each other for the common goal improving the generic programming. References [1] P. Grogono, Software Development with C++, Lecture Notes, Concordia University, Quebec, Canada, 2005 [2] J. Juran, L.F. Bic et al. Using Generic Programming Techniques in C++ with the Mac OS Toolbox, The Advanced Developers Hands on Conference, 2002, http://www.adhocconference.com/papers/2002/generic_p rogramming.pdf [3] A. Koenig, B. E. Moo, Accelerated C++, Pearson Education Inc., Boston, 2000 [4] Fr. Coenen, Inheritance, Lecture Notes, University of Liverpool, http://www.csc.liv.ac.uk/~frans/oldlectures/2cs45/oop/o op3.html [5] E. Vassev, Universum, COMP 6441 Term Project, Concordia University, Quebec, Canada, fall 2005 [6] Bjarne Stroustrup, Multiple Inheritance for C++, AT&T Bell Laboratories, Murray Hill, New Jersey, http://www-plan.cs.colorado.edu/diwan/class-papers/mi.pdf [7] Herb Sutter, Why Not Specialize Function Templates?, C/C++ Users Journal, 19(7), July 2001