Quels sont les foncteurs C ++ et leurs utilisations?


876

J'entends souvent parler de foncteurs en C ++. Quelqu'un peut-il me donner un aperçu de ce qu'ils sont et dans quels cas ils seraient utiles?


4
Ce sujet a été couvert en réponse à cette question: stackoverflow.com/questions/317450/why-override-operator#317528
Luc Touraille

2
Il est utilisé pour créer une fermeture en C ++.
copper.hat

En regardant les réponses ci-dessous, si quelqu'un se demande ce que cela operator()(...)signifie: cela surcharge l' opérateur "appel de fonction" . Il s'agit simplement d'une surcharge de l' ()opérateur pour l' opérateur. Ne vous méprenez pas operator()avec l'appel d'une fonction appelée operator, mais voyez-la comme la syntaxe de surcharge d'opérateur habituelle.
zardosht

Réponses:


1041

Un foncteur est à peu près juste 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 val) : x(val) {}  // Constructor
  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. La première est que, contrairement aux fonctions normales, elles 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 comme argument 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 bien 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 de même avec un pointeur de fonction normal, sauf que, comme je l'ai dit plus haut, les foncteurs peuvent être "personnalisés" car ils contiennent des états, ce qui les rend plus flexibles (si je voulais utiliser un pointeur de fonction, je devrais écrire une fonction qui a ajouté exactement 1 à son argument. Le foncteur est général, et ajoute tout ce que vous avez initialisé avec), et ils sont aussi potentiellement plus efficaces. Dans l'exemple ci-dessus, le compilateur sait exactement quelle fonction std::transformdoit appeler. Cela devrait appeler add_x::operator(). Cela signifie qu'il peut incorporer cet appel de fonction. Et cela le rend 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 voir immédiatement à quelle fonction il pointait, donc à moins qu'il n'effectue des optimisations globales assez complexes, il devrait déréférencer le pointeur au moment de l'exécution, puis passer l'appel.


32
Pouvez-vous expliquer cette ligne, veuillez std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); pourquoi vous y écrivez add_x, pas l'add42?
Alecs

102
@Alecs Les deux auraient fonctionné (mais l'effet aurait été différent). Si j'avais utilisé add42, j'aurais utilisé le foncteur que j'ai créé plus tôt et ajouté 42 à chaque valeur. Avec add_x(1)je crée une nouvelle instance du foncteur, une qui ajoute seulement 1 à chaque valeur. C'est simplement pour montrer que souvent, vous instanciez le foncteur "à la volée", quand vous en avez besoin, plutôt que de le créer d'abord, et de le conserver avant de l'utiliser réellement pour quoi que ce soit.
jalf

8
@zadane bien sûr. Ils doivent juste avoir le operator(), car c'est ce que l'appelant utilise pour l'invoquer. Ce que le foncteur a de plus sur les fonctions membres, les constructeurs, les opérateurs et les variables membres dépend entièrement de vous.
jalf

4
@ rikimaru2013 Dans le langage de la programmation fonctionnelle, vous avez raison, une fonction est également un foncteur, mais dans le langage de C ++, le foncteur est spécifiquement une classe utilisée comme fonction. La terminologie a été un peu abusée au début, mais la division est une distinction utile et persiste donc aujourd'hui. Si vous commencez à vous référer à des fonctions en tant que "foncteurs" dans un contexte C ++, alors vous allez simplement confondre la conversation.
srm

6
Est-ce une classe ou une instance de la classe? Dans la plupart des sources, add42serait appelé un foncteur, non add_x(qui est la classe du foncteur ou simplement la classe du foncteur). Je trouve que la terminologie est cohérente car les foncteurs sont également appelés objets fonction , pas classes de fonction. Pouvez-vous clarifier ce point?
Sergei Tachenov

121

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 la fonction boost :: bind et boost :: vous pouvez créer un foncteur à 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 ça, les messages d'erreur du compilateur ne sont pas lisibles par l'homme :)


4
Ne devrait pas operator ()être public dans votre premier exemple puisque les classes sont par défaut privées.
NathanOliver

4
Peut-être qu'à un moment donné, cette réponse mérite une mise à jour, car maintenant les lambdas sont le moyen le plus simple d'obtenir un foncteur de n'importe quoi
idclev 463035818

102

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 véritable avantage est qu'un foncteur peut tenir 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)
{ ....}

11
Il suffit d'ajouter qu'ils peuvent être utilisés comme un pointeur de fonction.
Martin York

7
@LokiAstari - Pour ceux qui sont nouveaux dans le concept, cela pourrait être un peu trompeur. Les foncteurs peuvent être «utilisés comme», mais pas toujours «à la place» des pointeurs de fonction. Par exemple, une fonction qui prend un pointeur de fonction ne peut pas prendre un foncteur à sa place même si le foncteur a les mêmes arguments et renvoie la même valeur que le pointeur de fonction. Mais dans l'ensemble, lors de la conception, les foncteurs sont la voie à suivre préférée et théoriquement "plus moderne".
MasonWinsauer

Pourquoi le second revient int-il alors qu'il devrait revenir bool? C'est C ++, pas C. Quand cette réponse a été écrite, n'existait boolpas?
Poursuite de Fund Monica

@QPaysTaxes Une faute de frappe je suppose. J'ai probablement copié le code du premier exemple et oublié de le changer. Je l'ai corrigé maintenant.
James Curran

1
@Riasat Si Matcher est dans une bibliothèque, définir Is5 () est assez simple. Et vous pouvez créer Is7 (), Is32 () etc. De plus, ce n'est qu'un exemple. Le foncteur pourrait être beaucoup plus compliqué.
James Curran

51

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

Utilisé à la place de la fonction simple:

Fonctionnalités:

  • L'objet 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:

Fonctionnalités:

  • L'objet fonction peut souvent être aligné

Les inconvénients:

  • L'objet fonction ne peut pas être échangé avec un autre type d'objet fonction lors de l'exécution (au moins à moins qu'il n'étende une classe de base, ce qui donne donc une surcharge)

Utilisé à la place de la fonction virtuelle:

Fonctionnalités:

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

Les inconvénients:

  • L'objet fonction ne peut pas être échangé avec un autre type d'objet fonction lors de l'exécution (au moins à moins qu'il n'étende une classe de base, ce qui donne donc une surcharge)

1
Pouvez-vous expliquer ces cas d'utilisation dans un exemple réel? comment utiliser les foncteurs comme pointeur de polymorphisme et de fonction?
Milad Khajavi

1
Que signifie réellement qu'un foncteur détient un état?
erogol

merci d'avoir souligné que l'on a besoin d'une classe de base pour avoir une sorte de polymorphisme. J'ai juste le problème que je dois utiliser un foncteur au même endroit qu'un simple pointeur de fonction et la seule façon que j'ai trouvée était d'écrire une classe de base de foncteur (car je ne peux pas utiliser des choses C ++ 11). Je n'étais pas sûr que cette surcharge ait du sens jusqu'à ce que je lise votre réponse.
idclev 463035818

1
@Erogol Un foncteur est un objet qui prend en charge la syntaxe foo(arguments). Par conséquent, il peut contenir des variables; par exemple, si vous aviez une update_password(string)fonction, vous voudrez peut-être garder une trace de la fréquence à laquelle cela s'est produit; avec un foncteur, qui peut être un private long timereprésentant du dernier horodatage. Avec un pointeur de fonction ou une fonction simple, vous devez utiliser une variable en dehors de son espace de nom, qui n'est directement liée que par la documentation et l'utilisation, plutôt que par définition.l
Fund Monica's Lawsuit

4
⁺¹ pour avoir mentionné que le nom a été inventé sans raison. Je viens de chercher quelle est la relation entre le foncteur mathématique (ou fonctionnel si vous voulez) et celui de C ++.
Hi-Angel

41

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 car ils peuvent conserver l'état avant et entre les appels de fonction, comme une fermeture dans les langages fonctionnels. Par exemple, vous pouvez définir un MultiplyByfoncteur 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 pouvez passer un MultiplyByobjet à 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 par rapport à un pointeur sur une fonction est que l'appel peut être aligné dans plusieurs cas. Si vous avez passé un pointeur de fonction à transform, à moins que cet appel ne soit en ligne et que le compilateur sache que vous lui passez toujours la même fonction, il ne peut pas aligner l'appel via le pointeur.


37

Pour les débutants comme moi parmi nous: après quelques recherches, j'ai compris ce qu'a fait le code jalf publié.

Un foncteur est une classe ou un objet struct qui peut être "appelé" comme une fonction. Ceci est rendu possible en surchargeant le () operator. Le () operator(pas sûr de son nom) peut accepter n'importe quel nombre d'arguments. Les autres opérateurs n'en prennent que deux, c'est-à-dire qu'ils + operatorne peuvent prendre que deux valeurs (une de chaque côté de l'opérateur) et renvoyer la valeur pour laquelle vous l'avez surchargée. Vous pouvez adapter n'importe quel nombre d'arguments à l'intérieur d'un () operatorqui lui donne sa flexibilité.

Pour créer un foncteur, vous créez d'abord 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 deux-points, quelque chose que je connaissais également) qui construit les objets membres de la classe avec le paramètre précédemment déclaré au constructeur. Ensuite, le () operatorest 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!


1
L'opérateur () est appelé opérateur d'appel de fonction. Je suppose que vous pourriez aussi l'appeler l'opérateur entre parenthèses.
Gautam

4
"Ce paramètre est en fait l'argument" parameterVar "passé par le constructeur que nous venons d'écrire" Huh?
Courses de légèreté en orbite du

22

Un foncteur est une fonction d'ordre supérieur qui applique une fonction aux types paramétrés (c'est-à-dire modèles). Il s'agit d'une généralisation de la fonction d'ordre supérieur de la carte . Par exemple, nous pourrions définir un foncteur std::vectorcomme ceci:

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 a std::vector<T>et retourne std::vector<U>quand on lui donne une fonction Fqui prend a Tet retourne a U. Un foncteur ne doit pas être défini sur les types de conteneurs, il peut également être défini pour tout type de modèle, notamment 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)));
}

Voici un exemple simple qui convertit le type en 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);

Les foncteurs doivent suivre deux lois. La première est la loi sur l'identité, qui stipule que si le foncteur se voit attribuer une fonction d'identité, cela devrait être la même chose que d'appliquer la fonction d'identité au type, c'est-à-dire qu'il 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 se voit attribuer une composition de deux fonctions, cela devrait être la même chose que d'appliquer le foncteur pour la première fonction, puis à nouveau pour la deuxième fonction. Donc, 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));

2
Article soutenant que le foncteur devrait être correctement utilisé pour ce sens (voir aussi en.wikipedia.org/wiki/Functor ), et que son utilisation pour les objets de fonction est tout simplement bâclée: jackieokay.com/2017/01/26/functors.html It peut être trop tard pour cela, étant donné le nombre de réponses ici qui ne considèrent que la signification de l'objet fonction.
armb le

2
Cette réponse devrait être celle avec> 700 Upvotes. En tant que personne qui connaît mieux Haskell que C ++, le lingua C ++ m'a toujours laissé perplexe.
mschmidt

Théorie des catégories et C ++? S'agit-il du compte SO secret de Bartosz Milewski?
Mateen Ulhaq

1
Il pourrait être utile de résumer les lois du foncteur en notation standard: fmap(id, x) = id(x)et fmap(f ◦ g, x) = fmap(f, fmap(g, x)).
Mateen Ulhaq

@mschmidt tandis que functor signifie également cela, C ++ surcharge le nom pour qu'il signifie la même chose que "objet de fonction"
Caleth

9

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 elles), et elles sont toutes identiques, sauf que chacune 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 un pointeur de fonction et l'appellerais simplement dans les 3 points. (Le code ne doit donc apparaître qu'une seule fois, au lieu de vingt.)

Mais j'ai réalisé, dans chaque cas, que la fonction spécifique nécessitait un profil de paramètre 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 redéfinie dans une classe dérivée. Mais est-ce que je veux vraiment construire tout cela HÉRITAGE, juste pour que je puisse passer un pointeur de fonction ????

SOLUTION: Donc, ce que j'ai fait, j'ai créé une classe wrapper (un "Functor") qui est capable d'appeler n'importe quelle fonction dont j'avais besoin. Je l'ai configuré à l'avance (avec ses paramètres, etc.) puis je le passe au lieu 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'en avais besoin d'appeler 3 fois.)


C'est tout - 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.


3
Si votre foncteur a appelé différentes fonctions spécifiques, et que ces autres fonctions variaient dans le nombre de paramètres qu'ils acceptent, cela signifie-t-il que votre foncteur a accepté un nombre variable d'arguments pour la répartition vers ces autres fonctions?
johnbakers

4
pouvez-vous s'il vous plaît expliquer le scénario ci-dessus en citant une partie du code, je suis nouveau sur c ++ je veux comprendre ce concept ..
sanjeev

3

À l'exception de ceux utilisés dans le rappel, les foncteurs C ++ peuvent également aider à fournir un style d'accès Matlab à une classe matricielle . Il y a un exemple .


Ceci (l'exemple de matrice) est une utilisation simple des operator()propriétés d'objet fonction, mais ne les utilise pas.
renardesque

3

Comme cela a été répété, les foncteurs sont des classes qui peuvent être traitées comme des fonctions (opérateur de surcharge ()).

Ils sont particulièrement utiles dans les situations où vous devez associer certaines données à des appels répétés ou différés à une fonction.

Par exemple, une liste chaînée de foncteurs peut être utilisée pour implémenter un système de coroutine synchrone basique à faible surcharge, un répartiteur de tâches ou une analyse de fichiers interruptible. Exemples:

/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
    std::string output;
    Functor(const std::string& out): output(out){}
    operator()() const
    {
        std::cout << output << " ";
    }
};

int main(int argc, char **argv)
{
    std::list<Functor> taskQueue;
    taskQueue.push_back(Functor("this"));
    taskQueue.push_back(Functor("is a"));
    taskQueue.push_back(Functor("very simple"));
    taskQueue.push_back(Functor("and poorly used"));
    taskQueue.push_back(Functor("task queue"));
    for(std::list<Functor>::iterator it = taskQueue.begin();
        it != taskQueue.end(); ++it)
    {
        *it();
    }
    return 0;
}

/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
    std::cout << "i = " << i << std::endl;
    std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
    std::cin >> should_increment;
    return 2;
}
void doSensitiveWork()
{
     ++i;
     should_increment = false;
}
class BaseCoroutine
{
public:
    BaseCoroutine(int stat): status(stat), waiting(false){}
    void operator()(){ status = perform(); }
    int getStatus() const { return status; }
protected:
    int status;
    bool waiting;
    virtual int perform() = 0;
    bool await_status(BaseCoroutine& other, int stat, int change)
    {
        if(!waiting)
        {
            waiting = true;
        }
        if(other.getStatus() == stat)
        {
            status = change;
            waiting = false;
        }
        return !waiting;
    }
}

class MyCoroutine1: public BaseCoroutine
{
public:
    MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
    BaseCoroutine& partner;
    virtual int perform()
    {
        if(getStatus() == 1)
            return doSomeWork();
        if(getStatus() == 2)
        {
            if(await_status(partner, 1))
                return 1;
            else if(i == 100)
                return 0;
            else
                return 2;
        }
    }
};

class MyCoroutine2: public BaseCoroutine
{
public:
    MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
    bool& work_signal;
    virtual int perform()
    {
        if(i == 100)
            return 0;
        if(work_signal)
        {
            doSensitiveWork();
            return 2;
        }
        return 1;
    }
};

int main()
{
     std::list<BaseCoroutine* > coroutineList;
     MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
     MyCoroutine1 *printer = new MyCoroutine1(incrementer);

     while(coroutineList.size())
     {
         for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
             it != coroutineList.end(); ++it)
         {
             *it();
             if(*it.getStatus() == 0)
             {
                 coroutineList.erase(it);
             }
         }
     }
     delete printer;
     delete incrementer;
     return 0;
}

Bien sûr, ces exemples ne sont pas très utiles en eux-mêmes. Ils montrent seulement comment les foncteurs peuvent être utiles, les foncteurs eux-mêmes sont très basiques et inflexibles et cela les rend moins utiles que, par exemple, ce que fournit le boost.


2

Les foncteurs sont utilisés dans gtkmm pour connecter un bouton GUI à une fonction ou une méthode C ++ réelle.


Si vous utilisez la bibliothèque pthread pour rendre votre application multithread, Functors peut vous aider.
Pour démarrer un thread, l'un des arguments de la pthread_create(..)est le pointeur de fonction à exécuter sur son propre thread.
Mais il y a un inconvénient. Ce pointeur ne peut pas être un pointeur vers une méthode, à moins qu'il ne s'agisse d'une méthode statique ou à moins que vous ne spécifiiez sa classe , comme class::method. Et autre chose, l'interface de votre méthode ne peut être que:

void* method(void* something)

Vous ne pouvez donc pas exécuter (de manière simple et évidente) les méthodes de votre classe dans un thread sans faire quelque chose de plus.

Un très bon moyen de gérer les threads en C ++ consiste à créer votre propre Threadclasse. Si vous vouliez exécuter des méthodes à partir d'une MyClassclasse, ce que j'ai fait était de transformer ces méthodes en Functorclasses dérivées.

De plus, la Threadclasse a cette méthode: static void* startThread(void* arg)
Un pointeur vers cette méthode sera utilisé comme argument à appeler pthread_create(..). Et ce qui startThread(..)devrait recevoir dans arg est une void*référence castée à une instance dans le tas de n'importe quelle Functorclasse dérivée, qui sera castée en arrière Functor*lors de son exécution, puis appelée sa run()méthode.


2

Pour ajouter, j'ai utilisé des objets de fonction pour adapter une méthode héritée existante au modèle de commande; (seul endroit où la beauté du paradigme OO vrai OCP je me sentais); Ajoutant également ici le modèle d'adaptateur de fonction associé.

Supposons que votre méthode ait la signature:

int CTask::ThreeParameterTask(int par1, int par2, int par3)

Nous verrons comment nous pouvons l'adapter au modèle de commande - pour cela, vous devez d'abord écrire un adaptateur de fonction membre afin qu'il puisse être appelé en tant qu'objet fonction.

Remarque - c'est moche, et vous pouvez peut-être utiliser les aides de liaison Boost, etc., mais si vous ne pouvez pas ou ne voulez pas, c'est une façon.

// a template class for converting a member function of the type int        function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
  public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
    :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
    return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

De plus, nous avons besoin d'une méthode d'aide mem_fun3 pour la classe ci-dessus pour faciliter l'appel.

template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm)          (_arg1,_arg2,_arg3) )
{
  return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));

}

Maintenant, pour lier les paramètres, nous devons écrire une fonction de liant. Alors, c'est parti:

template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
    :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}

 //and this is the function object 
 void operator()() const
 {
        m_fn(m_ptr,m1,m2,m3);//that calls the operator
    }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

Et, une fonction d'aide pour utiliser la classe binder3 - bind3:

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

Maintenant, nous devons l'utiliser avec la classe Command; utilisez le typedef suivant:

typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
    method3 = p_method;
}

Voici comment vous l'appelez:

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
      &CTask::ThreeParameterTask), task1,2122,23 );

Remarque: f3 (); appellera la méthode task1-> ThreeParameterTask (21,22,23) ;.

Le contexte complet de ce modèle sur le lien suivant


2

Un grand avantage de l'implémentation de fonctions en tant que foncteurs est qu'elles peuvent maintenir et réutiliser l'état entre les appels. Par exemple, de nombreux algorithmes de programmation dynamique, comme l'algorithme de Wagner-Fischer pour calculer la distance Levenshtein entre les chaînes, fonctionnent en remplissant un grand tableau de résultats. Il est très inefficace d'allouer cette table à chaque appel de la fonction, donc l'implémentation de la fonction en tant que foncteur et la transformation de la table en variable membre peuvent grandement améliorer les performances.

Voici un exemple d'implémentation de l'algorithme Wagner-Fischer en tant que foncteur. Remarquez comment la table est allouée dans le constructeur, puis réutilisée dans operator(), avec un redimensionnement si nécessaire.

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};

1

Functor peut également être utilisé pour simuler la définition d'une fonction locale au sein d'une fonction. Reportez-vous à la question et à une autre .

Mais un foncteur local ne peut pas accéder aux variables automatiques externes. La fonction lambda (C ++ 11) est une meilleure solution.


-10

J'ai "découvert" une utilisation très intéressante des foncteurs: je les utilise quand je n'ai pas un bon nom pour une méthode, car un foncteur est une méthode sans nom ;-)


Pourquoi décrivez-vous un foncteur comme une "méthode sans nom"?
Anderson Green

5
Une fonction sans nom est appelée lambda.
Paul Fultz II
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.