Here, you can find a solution to a well known problem of multimethods. It will be demonstrated using famous "crossing shapes example".
The main merits of the proposed solution are:
- no use of type casts of any kind (dynamic, static, reinterpret, const or C-style);
- no use of RTTI;
- no use of preprocessor;
- strong type safety;
- constant time of multimethod execution;
- no dynamic memory allocation (via new or
malloc) is performed during multimethod call;
- compiler doing all the work;
- use of only standard C++ features.
This approach makes it possible for user to write quite readable code. Suppose we have the following hierarchy of shapes:
---- Shape_ ------
| | |
Circle_ Rect_ Triangle_
Then, user can write code like this:
Shape_* circle = new Circle_( Point_( 5, 5 ), 3 );
Shape_* rect = new Rect_( Point_( 5, 5 ), Point_( 10, 10 ) );
bool cross1 = circle->Cross( rect );
bool cross2 = rect->Cross( circle );
Multimethod is called in the following form:
(arg0)->Cross( arg1 )
where types of both
TArg1, respectively) are unknown at compile time. At runtime, the
TArg0 type is detected via
virtual method call (method
Cross) and the
TArg1 - via use of 'deferred dispatching' pattern (Visitor-like pattern, where nodes can be declared independently of visitor).
User is responsible for providing implementation of crossing detection code for situations where types
TArg1 are known. Implementations are provided in the form of template class specialization. For example:
struct Crossing_< Circle_, Rect_ >
One can provide some (or all) of the following:
- fully specialized versions of
Crossing_ (circles against rectangles, for example),
- symmetric versions of
Crossing_ (rectangles against circles via circles against rectangles crossing, for example),
- hierarchy depended versions (crossing of two rectangular shapes, for example),
- fully specialized homogeneous versions (crossing of two circles, for example),
The details are provided in the form of well-commented code in demo project (in file mmvdd_initial.cpp).
- standard compliant C++ compiler
Keep in mind that some obvious improvements are missed to keep readability.
Some improvements of the initial solution will be provided below.
Deferred Dispatcher Generation
Someone thinks that manual writing of dispatcher is awkward. But we can generate a dispatcher from repository typelist, that contains types of all used concrete shapes.
- Suppose there are two libraries of shapes and types provided by them which are joined in two typelists:
Shapes1. One can merge these typelists and use these libraries together.
- Typelists provided by libraries can be easily modified. One can add or remove some shapes.
- Compilation time may grow.
- Number of shape types is limited by standard (by template recursion limitation).
The details of this improvement are provided in the form of well-commented code in demo project (in file mmvdd_generated.cpp).
- Andrei Alexanderscu "Modern C++ Design"
- David Vandevoorde, Nicolai M. Josuttis "C++ Templates: The Complete Guide"
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides "Design Patterns: Elements of Reusable Object-Oriented Software"
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.