2. C++ Avancé
2.10. Functors, adapters, Fonctions Lambda
2.10.1. Qu'est ce qu'un functor ?
Un foncteur (Functor en anglais) encore appelé objet-fonction
est un objet pour lequel l'opérateur ()
a été redéfini.
Un foncteur est passé en paramètre des algorithmes de la STL afin de
donner un comportement spécifique.
Voici un exemple qui trie un vector de personnes suivant leurs ages
et calcule le nombre d'adultes du vecteur.
#include <iostream>
#include <algorithm>
#include <iterator>
#include <functional>
#include <vector>
using namespace std;
/**
* generic function used to display contents of container
*/
template<class Container>
void print(Container& c) {
for (auto x : c) {
cerr << x << endl;
}
}
#define cdump(msg, v) cerr << msg << endl << #v << " = [" << endl; \
print(v); \
cerr << "]" << endl;
/**
* a Person
*/
class Person {
protected:
string name;
int age;
public:
Person(string n, int a) : name(n), age(a) {
}
friend ostream& operator<<(ostream& out, Person& p) {
out << p.name << ", " << p.age;
return out;
}
friend ostream& operator<<(ostream& out, Person *p) {
out << p->name << ", " << p->age;
return out;
}
int get_age() {
return age;
}
string get_name() {
return name;
}
};
// ==================================================================
// version for Person&
// ==================================================================
/**
* Functor defined as a struct that compares the ages of
* two Persons
*/
struct PersonSortByAgeFunctor {
bool operator()(const Person& lhs, const Person& rhs) {
return const_cast<Person&>(lhs).get_age() <
const_cast<Person&>(rhs).get_age();
}
};
/**
* Functor defined as a class that compares the age of
* a Person to some limit
*/
class PersonAdultCounter {
protected:
int limit;
public:
PersonAdultCounter(int l) : limit(l) {
}
bool operator()(const Person& rhs) {
return const_cast<Person&>(rhs).get_age() >= limit;
}
};
/**
* version for reference to Person
*/
void test_person() {
// use vector of Person
vector<Person> vp;
vp.push_back(Person("donald", 40));
vp.push_back(Person("riri", 10));
vp.push_back(Person("fifi", 12));
vp.push_back(Person("picsou", 60));
vp.push_back(Person("loulou", 11));
sort(vp.begin(), vp.end(), PersonSortByAgeFunctor());
cdump("print persons", vp);
int n_adults = count_if(vp.begin(), vp.end(), PersonAdultCounter(18));
cerr << "there are " << n_adults << " adults" << endl;
}
// ==================================================================
// version for Person *
// ==================================================================
/**
* Functor defined as a struct that compares the ages of
* two pointers to Persons
*/
struct PtrPersonSortByAgeFunctor {
bool operator()(const Person *lhs, const Person *rhs) {
return const_cast<Person *>(lhs)->get_age() <
const_cast<Person *>(rhs)->get_age();
}
};
/**
* Functor defined as a class that compares the age of
* a pointer to Person to some limit
*/
class PtrPersonAdultCounter {
protected:
int limit;
public:
PtrPersonAdultCounter(int l) : limit(l) {
}
bool operator()(const Person *rhs) {
return const_cast<Person *>(rhs)->get_age() >= limit;
}
};
/**
* version for pointer of Person
*/
void test_ptr_of_person() {
// use vector of Person *
vector<Person *> vpp;
vpp.push_back(new Person("donald", 40));
vpp.push_back(new Person("riri", 10));
vpp.push_back(new Person("fifi", 12));
vpp.push_back(new Person("picsou", 60));
vpp.push_back(new Person("loulou", 11));
sort(vpp.begin(), vpp.end(), PtrPersonSortByAgeFunctor());
cdump("print persons", vpp);
int n_adults = count_if(vpp.begin(), vpp.end(), PtrPersonAdultCounter(18));
cerr << "there are " << n_adults << " adults" << endl;
}
/**
* main function
*/
int main() {
test_person();
test_ptr_of_person();
return 0;
}
print persons
vp = [
riri, 10
loulou, 11
fifi, 12
donald, 40
picsou, 60
]
there are 2 adults
print persons
vpp = [
riri, 10
loulou, 11
fifi, 12
donald, 40
picsou, 60
]
there are 2 adults
2.10.2. Qu'est ce qu'un adapter ?
Un Adaptateur (Adapter en anglais) sont des foncteurs de
la STL
On distingue 4 types d'adaptateurs :
- Binders
- convertissent les objets binaires en objets unaires en fixant l'un
des arguments de () à une constante par exemple
bind1st, bind2nd
- Negators
- modifient le comportement en inversant le sens (ex: not1,
not2)
- Adaptors for pointers to functions
- permettent aux pointeurs vers des fonctions unaires ou binaire de
fonctionner avec les adaptateurs de la STL (ex: pointer_to_binary_function)
- Adaptors for pointers to member functions
- permettent aux pointeur sur des fonctions membres unaires ou binaires
de fonctionner avec les adatateurs de la STL (ex: mem_fun_t)
#include <iostream>
#include <algorithm>
#include <iterator>
#include <functional>
#include <vector>
using namespace std;
/**
* generic function used to display contents of container
*/
template<class Container>
void print(Container& c) {
for (auto x : c) {
cerr << x << " ";
}
cerr << endl;
}
#define cdump(msg, v) cerr << msg << endl << #v << " = "; print(v);
/**
* a Person: user-defined class
*/
class Person {
protected:
string name;
int age;
public:
Person(string n, int a) : name(n), age(a) {
}
friend ostream& operator<<(ostream& out, Person& p) {
out << p.name << ", " << p.age;
return out;
}
friend ostream& operator<<(ostream& out, Person *p) {
out << p->name << ", " << p->age;
return out;
}
int get_age() {
return age;
}
string get_name() {
return name;
}
void print() {
cout << name << endl;
}
};
/**
* user defined function that returns -x given x
*/
int my_negate(int x) {
return -x;
}
/**
* main function
*/
int main() {
vector<int> v = { 1, -2, 3, -4, 5, -6 };
cdump("initial v", v);
// use of negate
transform(v.begin(), v.end(), v.begin(), negate<int>());
// or
transform(v.begin(), v.end(), v.begin(), my_negate);
cdump("use of adapter.negator ", v);
// use of multiplies
transform(v.begin(), v.end(), // source
v.begin(), // second source
v.begin(), // destination
multiplies<int>()); // operation
cdump("use of adapter.multiply ", v);
// use of bind2nd and greater
// replace each value greater than 20 by the value -5
replace_if(v.begin(), v.end(),
bind2nd(greater<int>(), 20), -5);
cdump("use of adapter.bind2nd ", v);
// use of mem_fun_ref for vector of Person
vector<Person> vp;
vp.push_back(Person("riri", 10));
vp.push_back(Person("fifi", 12));
vp.push_back(Person("loulou", 11));
for_each(vp.begin(), vp.end(), std::mem_fun_ref(&Person::print));
cdump("print names", vp);
// use of mem_fun_ for vector of pointer to Person
vector<Person *> vpp;
vpp.push_back(new Person("riri", 10));
vpp.push_back(new Person("fifi", 12));
vpp.push_back(new Person("loulou", 11));
for_each(vpp.begin(), vpp.end(), std::mem_fun(&Person::print));
cdump("print names", vpp);
return 0;
}
initial v
v = 1 -2 3 -4 5 -6
use of adapter.negator
v = -1 2 -3 4 -5 6
use of adapter.multiply
v = 1 4 9 16 25 36
use of adapter.bind2nd
v = 1 4 9 16 -5 -5
riri
fifi
loulou
print names
vp = riri, 10 fifi, 12 loulou, 11
riri
fifi
loulou
print names
vpp = riri, 10 fifi, 12 loulou, 11
2.10.3. Qu'est ce qu'une fonction lambda ?
Il s'agit d'un mécanisme qui permet de déclarer une fonction
localement au moment où on en a besoin.
La fonction lambda n'a pas de nom puisque locale.
2.10.3.a Déclarer une fonction lambda
[capture_block](parameters) mutable exception -> return_type { body }
- capture_block : indique comment les variables externes à la fonction sont utilisées dans le corps de la fonction
- parameters : paramètres de la fonction (arguments)
- mutable (optionnel) : les variables externes à la fonction sont par défaut copiée et apparaissent comme constantes. Avec le mot clé mutable, elle sont modifiables mais toujours copiées (dupliquées).
En ce qui concerne la capture, on dispose de deux possibilités qui peuvent ensuite être combinées et énumérées sur les variables :
- [=] : capture toutes les variables par valeur
- [&] : capture toutes les variables par référence
Par exemple :
- [&x] : capture x par référence
- [=, &x] : capture toutes les variables par valeur, sauf x par référence
2.10.3.b Utiliser une fonction lambda
// définition mais non appelee
[] { cout << "hello" << endl; };
// définie et appelée
[] { cout << "hello" << endl; } ();
auto dbl = [](const int n){ return 2*n; };
// au lieu de
// int dbl(const int n) { return 2*n; };
int a = dbl(1);
int b = dbl(2);
Un autre exemple lié à la STL :
#include <vector>
#include <iterator>
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
vector<int> v(10);
int index = 0;
// generate vector elements with lambda function
// incrementing index an extern variable
generate(v.begin(), v.end(), [&index]() {
return index++;
});
// print vector contents
cout << "v = [";
copy(v.begin(), v.end(), ostream_iterator<int>(cout," "));
cout << "]" << endl;
// sort in reverse order with lambda function
sort(v.begin(), v.end(), [](int a, int b) -> bool {
return a > b;
});
// print vector contents using for_each
cout << "v = (sorted) [";
for_each(v.begin(), v.end(), [](int i){
cout << i << " ";
});
cout << "]" << endl;
return 0;
}
v = [0 1 2 3 4 5 6 7 8 9 ]
v = (sorted) [9 8 7 6 5 4 3 2 1 0 ]