220 likes | 231 Views
This presentation explores an extension to the subtype relationship in C++, allowing for maximal separation of functionalities and collaboration-based design. Practical experiences and examples are provided.
E N D
An extension to the subtype relationship in C++ István Zólyomi, Zoltán Porkoláb and Tamás Kozsik Eötvös Loránd University, Budapest, Hungary {scamel | gsd | kto}@elte.hu
The structure of this presentation • Motivation with examples • Loki::Typelist (short reminder) • Creating the Family hierarchy • Converting between families • Inclusion polymorphism (FamilyPtr and FamilyRef) • Practical experiences • Summary
Motivation • Maximal separation of functionalities • Collaboration based design • Better maintenanceof code • Easier to create classes with required behavior • Create and handle classes as a collection of independent components
Example 1 Vehicle Emergency Car Truck EmergencyVehicle PoliceCar FireEngine
ios virtual virtual virtual istream ostream iostream fstreambase ifstream ofstream fstream
Possible solutions • Virtual inheritance • Intrusive: specified in base classes • More concerns implies exponential number of virtual bases • Traits • No subtype relationship using hierarchies in traits • AOP, MDSC, CF • are for slightly different problems • are extensions to standard C++ • Families: subtype relationship implemented with metaprogramming • Non-intrusive solution based on the C++ standard
Terminology • Class: implementation of a concern • Orthogonal concerns in independent hierarchies • Collaboration-based design: combining concerns • Collect concerns to gain required behavior • Family: instrument to express collaboration of concerns • Implemented as a single class template • Collects concern classes as a mixin using multiple inheritance • OO languages support conversions to a single concern (base) • do not support conversions between families (collections of concerns)
Example 2 (Harold Ossher) OpEval OpCheck OpDisplay Operator PlusEval PlusCheck PlusDisplay Plus
Loki::Typelist • Introduced by Andrei Alexandrescu (and others) • Handles meta-information (types) in compile time like conventional containers do with data in runtime <class Head, class Tail> struct Typelist { … }; • Any number of types can be listed using recursion typedef Typelist<char, Typelist<short, Typelist<int, NullType> > > Scalars; • The last element of every list is Loki::NullType by convention(like \0 for C-strings)
Loki::Typelist (cont.) • We can make the previous class definition linear with predefined macros with NullType included • Typelist has many operations in Loki: appending, indexed access, removing duplicates, etc typedef TYPELIST_3(char,short,int) TheSameList; typedef Append<Scalars, TYPELIST_1(long)>::Result ExtendedList;
Source code of family construction template <class List> struct Family; template <class Head, class Tail> struct Family< Typelist<Head,Tail> > : public Head, public Family<Tail> { // --- Type name shortcuts typedef Family<Tail> Rest; typedef Family< Typelist<Head,Tail> > MyType; // --- Copy constructor Family(const MyType& mt) : Head(mt), Rest(mt) {} // --- "Recursive" constructor Family(const Head& head, const Rest& rest): Head(head), Rest(rest) {} }; template <class Head> struct Family< Typelist<Head,NullType> > : public Head { // --- All in one constructor Family(const Head& head) : Head(head) {} };
Conversions between families • Initialize the head class for each recursion step • Template constructors provide conversion steps (the same for operator=) template <class Head, class Tail> template <class FromType> Family< Typelist<Head,Tail> > :: Family(const FromType& f): Head(f), Family<Tail>(f) {} • Example of usage: FAMILY_3(PlusEval,PlusDisplay, PlusCheck) sum; FAMILY_2(OpEval,OpCheck) calculate; calculate = sum;
Advantages • Type safe: based on builtin language conversions • Efficient: no temporal families or objects are used, objects are initialized directly • General and transparent: not restricted to families only, any user object can be converted without explicit conversion call. struct Minus: public MinusEval, public MinusDisplay, public MinusCheck {}; Minus subtract; calculate = subtract;
Limitations • No duplicates in typelists or compile error • Keyword explicit is suppressed because of explicit constructor calls during conversion • Compilation fails if there is no default or copy constructor and assignment operator
Smart pointers • Problems: • Conversion copies objects by value (slicing) • No dynamic binding • Solution: smart pointers • Implementation and usage similar to those of Family FAMILY_3(PlusDisplay, PlusEval, PlusCheck) sum; FAMILYPTR_2(OpDisplay, OpEval) opPtr(sum); // --- Function call with explicit cast static_cast<OpDisplay*>(opPtr)->show(); // --- In longer form with implicit cast OpDisplay *displayPtr = opPtr; displayPtr->show();
FAMILYPTR_1(OpEval) OpEval *head FAMILYPTR_2(OpDisplay,OpEval) OpDisplay *head FAMILYPTR_2(OpDisplay, OpEval) opPtr; points to head: OpDisplay* head: OpEval* points to PlusDisplay PlusCheck PlusEval
FamilyRef • Similar to FamilyPtr • Store references instead of pointers • Consequences: • A FamilyRef object must be initialized • Initializes by reference but copies by value during assignment FAMILY_3(PlusDisplay, PlusEval, PlusCheck) sum; // --- Initializes by reference FAMILYREF_3(OpDisplay, OpEval, OpCheck) exprRef(sum); // --- Copies by value exprRef = sum;
#include <iostream> #include "family.h" struct Shape { virtual void f() { std::cout <<"Shape"; } }; struct Circle : public Shape { void f() { std::cout <<"Circle"; } }; struct Colored {}; struct Filled {}; void main() { FAMILY_3(Circle,Colored, Filled) extCircle; FAMILY_3(Colored, Filled, Shape)extShape(extCircle); FAMILY_2(Filled, Colored)extensions(extCircle); extensions = extShape = extCircle; FAMILYPTR_3(Colored, Filled, Shape)extShapePtr(extCircle); Shape* shapePtr = extShapePtr; shapePtr->f(); // --- Prints “Circle” FAMILYPTR_3(Filled, Colored, Circle)extCirclePtr(extCircle); extShapePtr = extCircle; // --- Object -> Pointer extShapePtr = extCirclePtr; // --- Der. Pointer -> Base Pointer extShape = extShapePtr; // --- Pointer -> Object (by value) }
Summary • Support for collaboration based design • Implementation uses Loki::Typelist • Non-intrusive solution • Easy to understand and manage • No extension to C++ • Bad experience with compilers vs standards • Features: • Composition of concerns in a single class (Family) • Conversion between related families • Dynamic binding with smart pointers and references
scamel@elte.hu gsd@elte.hu kto@elte.hu Eötvös Loránd University, Budapest, Hungary István Zólyomi Zoltán Porkoláb Tamás Kozsik Download source from http://gsd.web.elte.hu