J'entends beaucoup parler des foncteurs en C ++, quelqu'un peut-il me donner un aperçu de ce qu'ils sont et dans quels cas ils seraient utiles?
Réponses
Trop de publicités?Un foncteur est à peu près juste une classe qui définit l'opérateur(). Qui vous permet de créer des objets qui "ressemble" à une fonction:
// this is a functor
struct add_x {
add_x(int x) : x(x) {}
int operator()(int y) { return x + y; }
private:
int x;
};
// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument
std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out;
// Pass a functor to std::transform, which calls the functor on every element
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1));
assert(out[i] == in[i] + 1); // for all i
Il y a un couple de belles choses à propos de foncteurs. L'une est que contrairement à des fonctions, elles peuvent contenir de l'état. L'exemple ci-dessus crée une fonction qui ajoute 42 à tout ce que vous donnez. Mais cette valeur 42 n'est pas codé en dur, il a été spécifié comme un argument du constructeur lorsque nous avons créé notre foncteur instance. Je pourrais créer une autre additionneur, qui a ajouté le 27, tout en appelant le constructeur avec une valeur différente. Cela les rend bien personnalisable.
Comme les dernières lignes de présentation, vous devrez passer souvent foncteurs en tant qu'arguments d'autres fonctions telles que std::transform ou l'autre des algorithmes de la bibliothèque standard. On pourrait faire la même avec un pointeur de fonction, sauf, comme je l'ai dit ci-dessus, les foncteurs peuvent être "sur-mesure", car ils contiennent de l'état, en les rendant plus flexibles (Si je voulais utiliser un pointeur de fonction, je dois écrire une fonction qui a ajouté exactement 1 à son argument. Le foncteur est générale, et ajoute de ce que vous avez initialisé avec), et ils sont aussi potentiellement plus efficace. Dans l'exemple ci-dessus, le compilateur sait exactement quelle fonction std::transform
devrait appeler. Il doit appeler add_x::operator()
. Cela signifie qu'il peut inline cet appel de fonction. Et qui le rend tout aussi efficace que si j'avais manuellement appelé la fonction sur chaque valeur du vecteur.
Si j'avais passé un pointeur de fonction au lieu de cela, le compilateur ne pouvais pas voir immédiatement quelle fonction il points de, donc à moins qu'il effectue assez complexes optimisations globales, il faudrait déréférencer le pointeur au moment de l'exécution, et ensuite faire l'appel.
Peu plus. Vous pouvez utiliser boost::function
, pour créer des foncteurs de fonctions et de méthodes, comme ceci:
class Foo
{
void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"
et vous pouvez utiliser boost::bind pour ajouter de l'état à ce foncteur
boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"
et le plus utile, avec boost::bind et boost::function, vous pouvez créer foncteur de méthode de classe, en fait c'est un délégué:
class SomeClass
{
std::string state_;
public:
SomeClass(const char* s) : state_(s) {}
void method( std::string param )
{
std::cout << state_ << param << std::endl;
}
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"
Vous pouvez créer une liste ou un vecteur de foncteurs
std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
events.begin(), events.end(),
boost::bind( boost::apply<void>(), _1, e));
Il y a un problème avec tout ça, les messages d'erreur du compilateur n'est pas lisible par l'homme :)
Un Functor est un objet qui agit comme une fonction. Fondamentalement, une classe qui définit l'opérateur ().
class MyFunctor
{
public:
int operator()(int x) { return x * 2;}
}
MyFunctor doubler;
int x = doubler(5);
Le réel avantage est qu'un foncteur peut maintenir l'état.
class Matcher
{
int target;
public:
Matcher(int m) : target(m) {}
int operator()(int x) { return x == target;}
}
Matcher Is5(5);
if (Is5(n)) // same as if (n == 5)
{ ....}
Utilisé à la place de la plaine de la fonction:
Pour:
- Foncteur peut avoir l'état
- Foncteur s'inscrit dans la programmation orientée objet
Inconvénients:
- Il n'y a plus de saisie au clavier, un peu plus de temps pour la compilation etc.
Utilisé à la place du pointeur de fonction:
Pour:
- Foncteur souvent peut être incorporé
Inconvénients:
- Foncteur ne peut pas être échangé avec d'autres foncteur de type en cours d'exécution (à moins qu'il s'étend la classe de base, ce qui lui donne de certains frais généraux)
Utilisé à la place de polymorphisme:
Pour:
- Functor (non virtuel) ne nécessite pas vtable de l'exécution et d'expédition, il est donc plus efficace dans la plupart des cas
Inconvénients:
- Foncteur ne peut pas être échangé avec d'autres foncteur de type en cours d'exécution (à moins qu'il s'étend la classe de base, ce qui lui donne de certains frais généraux)
Comme d'autres l'ont mentionné, l'un foncteur est un objet qui se comporte comme une fonction, c'est à dire qu'il surcharges de l'opérateur d'appel de fonction.
Les foncteurs sont couramment utilisés dans les algorithmes de la STL. Ils sont utiles car ils peuvent contenir de l'état avant et entre les appels de fonction, comme une fermeture dans les langages fonctionnels. Par exemple, vous pouvez définir un MultiplyBy
foncteur qui multiplie le résultat de l'argument par une quantité spécifiée:
class MultiplyBy {
private:
int factor;
public:
MultiplyBy(int x) : factor(x) {
}
int operator () (int other) const {
return factor * other;
}
};
Ensuite, vous pouvez passer une MultiplyBy
objet d'un algorithme de type std::transform:
int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}
Un autre avantage d'un foncteur sur un pointeur à une fonction, c'est que l'appel peut être incorporé dans plus de cas. Si vous avez passé un pointeur de fonction à l' transform
, à moins que l' appel reçu inline et le compilateur sait que vous passez toujours la même fonction, il ne peut pas inline l'appel à l'aide du pointeur.