Explanations in the README
This commit is contained in:
parent
bf6537d994
commit
9f2a3e9132
1 changed files with 240 additions and 0 deletions
240
README.md
Normal file
240
README.md
Normal file
|
|
@ -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<class T>
|
||||
struc Increment
|
||||
{
|
||||
template<class OutIt>
|
||||
T operator() ( T & arg, OutIt out )
|
||||
{
|
||||
// do something with the `arg` and push it to `out`
|
||||
}
|
||||
/* ... */
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct Functor
|
||||
{
|
||||
Increment op;
|
||||
// the interface
|
||||
template<class OutIt>
|
||||
int operator() ( int & arg, OutIt out ) /* ... */
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct Assign : public Functor<T>
|
||||
{
|
||||
// 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<Increment> f(op);
|
||||
|
||||
// we must explictly declare the output iterator type at instanciation
|
||||
Assign<Increment,std::back_insert_iterator<std::vector<int> > > f(op);
|
||||
|
||||
// or use a helper that does not have a virtual function
|
||||
auto f = mafe_Functor<Assign>(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
|
||||
```
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue