From 9f2a3e913272032fb0c2dac29b90df0121c751d0 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Mon, 13 Jan 2014 09:43:02 +0100 Subject: [PATCH] Explanations in the README --- README.md | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..17536cf --- /dev/null +++ b/README.md @@ -0,0 +1,240 @@ +Functor pattern in C++: CRTP VS Inheritance +=========================================== + +Some examples to compare the performances of several implementations of the +Functor pattern: + +* Inheritance (the canonical way), +* Curiously Recurring Template Pattern (CRTP). + + +Abstract +-------- + +There is several way of implementing the functors in C++. This set of code +demonstrate two of them: an inheritance pattern and the CRTP. + +With this very rough test, the CRTP implementation seems to be the fastest. On +my machine, CRTP is approximately 1.3 times faster than Inheritance. + + +Introduction +------------ + +### Functor's definition + +The *Functor pattern* seems to have many possible definitions. Here, I will +consider the simpler one: a class type functor. This is basically an object that +is callable: a function object. + +```C++ +/* For example, this simple class uses the functor pattern to attach a state to +a function. */ +struct A: +{ + int _state; + A ( int state ) : _state(state) {} + int operator() ( int arg ) const { + return _state + arg; + } +}; +``` + +### Composition of function objects + +I really like this functor which is a good compromise between composition and +inheritance on one side, and object-oriented and functional programming on the +other side. + +I use it to assemble algorithms as a set of functions applied to a state. This +is a pattern heavily used within the [ParadisEO framework](http://paradiseo.gforge.inria.fr), +for instance. + +The targeted use case needs several function objects (called operators) to build +an algorithm (which is itself an operator), but here I will do the tests with a +single operator that embed an operator itself. + + +```C++ +/* An example of the general form of an operator and its usage */ +struct Algo +{ + OperatorInit _opi; + OperatorIter _opt; + Algo( OperatorInit & opi, OperatorIter opt ) : _opi(opi), _opt(opt) {} + int operator() ( int & state ) { + _opi(state); + for( int i=-1; i<9; ++i ) { + _opt(state); + } + return i; + } +}; + +int main( ) +{ + // Operators instanciation + OperatorInit init; + // They may take arguments + OperatorIter iter(1); + Algo algo( init, iter ); + int state = 0; + std::cout << algo( state ) << std::endl; +} +``` + +### Interfaces + +Of course, to permits the user of a framework to add new operators, we can use +interfaces as a way to force him to avoid mistakes. + +```C++ +/* With an inheritance pattern, we can use abstract classes as interfaces */ +struct Operator: +{ + virtual void operator() ( int & arg ) = 0; +}; + +struct OperatorInit : public Operator +{ + virtual void operator() ( int & arg ) { arg = 0; } +}; +``` + +### Generic programming + +As operators are used deep inside the call tree, they may need different +interfaces at each level, and not always the one defined by the framework. +For example, if there is interaction between user-defined operators somewhere +within a framework-defined one (remember operators are composed with operators). + +```C++ +/* Thus, we need templates, most probably for the manipulated state */ +template< class T> +struct OperatorIter +{ + // for example because this particular operator needs an access to "length" */ + int operator() ( T & state ) { state++; return state.length(); } +}; +``` + + +Putting it all together +----------------------- + +For the comparison, I will use here a simple example that will increment a +counter with the help of two functors. One is a generic `Assign` function that +is supposed to call an `Increment` function that alter the given state and push +it in a container. The other is the function that actually do the job. + +For simplicity, only the first one will use an interface called `Functor`. + +Keep in mind that this is just a simplified example that implements a hierarchy +of operators, it is not supposed to be elegant. + +```C++ +/* A simplified view of the logical articulation of the classes */ +template +struc Increment +{ + template + T operator() ( T & arg, OutIt out ) + { + // do something with the `arg` and push it to `out` + } + /* ... */ +}; + +template +struct Functor +{ + Increment op; + // the interface + template + int operator() ( int & arg, OutIt out ) /* ... */ +}; + +template +struct Assign : public Functor +{ + // Assemble operator[s] + Assign( Increment & op ) : Functor(op) {} + // This kind of interface + int operator() ( int & arg, OutIt out ) + { + return this->op(arg,out); + } + /* ... */ +}; +``` + + +Note about the implented versions +--------------------------------- + +The implementations are located in the following files: +* Inheritance version: `inheritance_functor.cpp` +* CRTP version: `crtp_functor_ttp.cpp` + +Notes about the code are written as comments in those files. + +### Inheritance code + +This version use overloading of the `Assign::operator()` function, over an abstract +base class that provide the interface. + +In the `Increment` functor, the output iterator template is defined over the +call function, which permits to ease the declaration of the functor. But in the +`Assign` class, as we want to overload a virtual function, it is not possible to +define such a template, it should be defined over the class itself. + +This is a limitation of this pattern, because I would have wanted to let the +compiler infer the output iterator type at call and thus avoid the need to +specify it at the instanciation. + +```C++ + // we cannot do that: + Assign f(op); + + // we must explictly declare the output iterator type at instanciation + Assign > > f(op); + + // or use a helper that does not have a virtual function + auto f = mafe_Functor(op,out); +``` + + +### Curiously Recurring Template Pattern + +Has there is no virtual functions involved in the CRTP, it is possible to +declare the output iterator template over the callable function, and the +compiler can infer its type. + +The use of the CRTP makes hard to design more than one level of "inheritance" +for the operator classes, and the template declarations are a pain. + + +Performance test +---------------- + +The performance test is done with two nested loops, the outer one instanciating +the functors and the inner one calling it several times. Both are iterating +10 000 times. + +To compile and run the performance tests, use the `./test.sh` script. + +``` +$ ./test.sh +inheritance_functor + +real 0m0.350s +user 0m0.344s +sys 0m0.004s + +crtp_functor_ttp + +real 0m0.272s +user 0m0.268s +sys 0m0.000s +``` +