Question Quels sont les foncteurs C ++ et leurs utilisations?


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?


704
2017-12-10 17:47


origine


Réponses:


Un foncteur est à peu près une classe qui définit l'opérateur (). Cela vous permet de créer des objets qui "ressemblent" à une fonction:

// this is a functor
struct add_x {
  add_x(int x) : x(x) {}
  int operator()(int y) const { 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(in.size());
// 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 quelques bonnes choses à propos des foncteurs. L'un est que contrairement aux fonctions régulières, ils peuvent contenir un état. L'exemple ci-dessus crée une fonction qui ajoute 42 à tout ce que vous lui donnez. Mais cette valeur 42 n'est pas codée en dur, elle a été spécifiée en tant qu'argument de constructeur lorsque nous avons créé notre instance de foncteur. Je pourrais créer un autre additionneur, qui a ajouté 27, juste en appelant le constructeur avec une valeur différente. Cela les rend joliment personnalisables.

Comme le montrent les dernières lignes, vous passez souvent des foncteurs comme arguments à d'autres fonctions telles que std :: transform ou les autres algorithmes de bibliothèque standard. Vous pouvez faire la même chose avec un pointeur de fonction normal, sauf que, comme je l'ai dit plus haut, les foncteurs peuvent être "personnalisés" car ils contiennent de l'état, ce qui les rend plus flexibles. ce qui ajoute exactement 1 à son argument: le foncteur est général, et ajoute tout ce que vous avez initialisé), et ils sont aussi potentiellement plus efficaces. Dans l'exemple ci-dessus, le compilateur sait exactement quelle fonction std::transform devrait appeler. Il devrait appeler add_x::operator(). Cela signifie qu'il peut aligner cet appel de fonction. Et cela le rend tout aussi efficace que si j'avais appelé manuellement la fonction sur chaque valeur du vecteur.

Si j'avais passé un pointeur de fonction à la place, le compilateur ne pouvait pas immédiatement voir à quelle fonction il pointait, donc à moins d'effectuer des optimisations globales assez complexes, il devrait déréférencer le pointeur à l'exécution, puis passer l'appel.


857
2017-12-10 17:58



Petit ajout. Vous pouvez utiliser boost::function, pour créer des foncteurs à partir de fonctions et de méthodes, comme ceci:

class Foo
{
public:
    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 un é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 functor à partir de la méthode class, 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 cela, les messages d'erreur du compilateur ne sont pas lisibles par l'homme :)


111
2017-12-10 19:15



Un Functor est un objet qui agit comme une fonction. Fondamentalement, une classe qui définit operator().

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) {}
     bool operator()(int x) { return x == target;}
}

Matcher Is5(5);

if (Is5(n))    // same as if (n == 5)
{ ....}

73
2017-12-10 17:58



Le nom "functor" a été traditionnellement utilisé dans théorie des catégories longtemps avant que C ++ apparaisse sur la scène. Cela n'a rien à voir avec le concept C ++ de functor. Il est préférable d'utiliser le nom objet de fonction au lieu de ce que nous appelons "functor" en C ++. C'est ainsi que d'autres langages de programmation appellent des constructions similaires.

Utilisé au lieu de la fonction simple:

Caractéristiques:

  • L'objet de fonction peut avoir un état
  • L'objet fonction s'intègre dans la POO (il se comporte comme tous les autres objets).

Les inconvénients:

  • Apporte plus de complexité au programme.

Utilisé à la place du pointeur de fonction:

Caractéristiques:

  • L'objet de fonction peut souvent être en ligne

Les inconvénients:

  • L'objet de fonction ne peut pas être échangé avec un autre type d'objet de fonction pendant l'exécution (au moins s'il étend une classe de base, ce qui entraîne une surcharge)

Utilisé à la place de la fonction virtuelle:

Caractéristiques:

  • L'objet de fonction (non virtuel) ne nécessite pas de distribution vtable et runtime, il est donc plus efficace dans la plupart des cas

Les inconvénients:

  • L'objet de fonction ne peut pas être échangé avec un autre type d'objet de fonction pendant l'exécution (au moins s'il étend une classe de base, ce qui entraîne une surcharge)

37
2017-11-21 16:59



Comme d'autres l'ont mentionné, un foncteur est un objet qui agit comme une fonction, c'est-à-dire qu'il surcharge l'opérateur d'appel de fonction.

Les foncteurs sont couramment utilisés dans les algorithmes STL. Ils sont utiles parce qu'ils peuvent contenir 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 son argument par un montant spécifié:

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

Ensuite, vous pourriez passer un MultiplyBy objet à un algorithme comme 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 vers une fonction est que l'appel peut être inséré dans plus de cas. Si vous avez passé un pointeur de fonction à transform, sauf si cette L'appel est devenu inline et le compilateur sait que vous lui transmettez toujours la même fonction, il ne peut pas aligner l'appel sur le pointeur.


34
2017-12-10 18:10



Pour les débutants comme moi parmi nous: après un peu de recherche j'ai compris ce que le code jalf posté a fait.

Un foncteur est un objet de classe ou de structure qui peut être "appelé" comme une fonction. Ceci est rendu possible en surchargeant le () operator. le () operator (je ne sais pas comment ça s'appelle) peut prendre n'importe quel nombre d'arguments. Les autres opérateurs n'en prennent que deux, c'est-à-dire + operator ne peut prendre que deux valeurs (une de chaque côté de l'opérateur) et retourner la valeur pour laquelle vous l'avez surchargé. Vous pouvez adapter n'importe quel nombre d'arguments dans un () operator ce qui lui donne sa flexibilité.

Pour créer un foncteur, vous devez d'abord créer votre classe. Ensuite, vous créez un constructeur pour la classe avec un paramètre de votre choix de type et de nom. Ceci est suivi dans la même déclaration par une liste d'initialisation (qui utilise un seul opérateur de deux-points, quelque chose à laquelle j'étais aussi nouveau) qui construit les objets membres de classe avec le paramètre précédemment déclaré au constructeur. Puis le () operator est surchargé. Enfin, vous déclarez les objets privés de la classe ou de la structure que vous avez créée.

Mon code (j'ai trouvé les noms de variables de jalf confus)

class myFunctor
{ 
    public:
        /* myFunctor is the constructor. parameterVar is the parameter passed to
           the constructor. : is the initializer list operator. myObject is the
           private member object of the myFunctor class. parameterVar is passed
           to the () operator which takes it and adds it to myObject in the
           overloaded () operator function. */
        myFunctor (int parameterVar) : myObject( parameterVar ) {}

        /* the "operator" word is a keyword which indicates this function is an 
           overloaded operator function. The () following this just tells the
           compiler that () is the operator being overloaded. Following that is
           the parameter for the overloaded operator. This parameter is actually
           the argument "parameterVar" passed by the constructor we just wrote.
           The last part of this statement is the overloaded operators body
           which adds the parameter passed to the member object. */
        int operator() (int myArgument) { return myObject + myArgument; }

    private: 
        int myObject; //Our private member object.
}; 

Si tout cela est inexact ou tout simplement faux, n'hésitez pas à me corriger!


28
2018-01-04 20:06



Un foncteur est un fonction d'ordre supérieur cela applique une fonction aux types paramétrés (c'est-à-dire modélisés). C'est une généralisation du carte fonction d'ordre supérieur. Par exemple, nous pourrions définir un foncteur pour std::vector comme ça:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

Cette fonction prend un std::vector<T> et retourne std::vector<U> quand donné une fonction F cela prend un T et renvoie un U. Un foncteur ne doit pas être défini sur des types de conteneur, il peut être défini pour tout type de modèle, y compris std::shared_ptr:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

Heres un exemple simple qui convertit le type en un double:

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

Il y a deux lois que les foncteurs devraient suivre. La première est la loi d'identité, qui stipule que si le foncteur reçoit une fonction d'identité, cela devrait être la même chose que d'appliquer la fonction d'identité au type, c'est-à-dire fmap(identity, x) devrait être le même que identity(x):

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

La loi suivante est la loi de composition, qui stipule que si le foncteur reçoit une composition de deux fonctions, cela devrait être le même que d'appliquer le foncteur pour la première fonction et ensuite pour la deuxième fonction. Alors, fmap(std::bind(f, std::bind(g, _1)), x) devrait être le même que fmap(f, fmap(g, x)):

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));

16
2018-05-06 05:47



Voici une situation réelle où j'ai été obligé d'utiliser un Functor pour résoudre mon problème:

J'ai un ensemble de fonctions (disons, 20 d'entre eux), et ils sont tous identiques, sauf que chacun appelle une fonction spécifique différente dans 3 endroits spécifiques.

C'est un gaspillage incroyable et une duplication de code. Normalement, je passerais simplement dans un pointeur de fonction, et j'appellerais ça dans les 3 points. (Ainsi, le code doit seulement apparaître une fois, au lieu de vingt fois.)

Mais ensuite, j'ai réalisé, dans chaque cas, que la fonction spécifique nécessitait un profil de paramètres complètement différent! Parfois 2 paramètres, parfois 5 paramètres, etc.

Une autre solution serait d'avoir une classe de base, où la fonction spécifique est une méthode surchargée dans une classe dérivée. Mais est-ce que je veux vraiment construire toute cette HÉRITAGE, juste pour que je puisse passer un pointeur de fonction ????

SOLUTION: Donc ce que j'ai fait, j'ai fait une classe wrapper (un "Functor") qui est capable d'appeler n'importe laquelle des fonctions dont j'avais besoin. Je l'ai mis en place à l'avance (avec ses paramètres, etc), puis je le passe à la place d'un pointeur de fonction. Maintenant, le code appelé peut déclencher le Functor, sans savoir ce qui se passe à l'intérieur. Il peut même l'appeler plusieurs fois (j'avais besoin d'appeler 3 fois).


Voilà - un exemple pratique où un Functor s'est avéré être la solution évidente et facile, ce qui m'a permis de réduire la duplication de code de 20 fonctions à 1.


8
2017-12-26 06:54