Question

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

Était-ce utile?

La solution

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 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 belles choses sur foncteurs. L'une est que, contrairement à des fonctions régulières, ils peuvent contenir l'état. L'exemple ci-dessus crée une fonction qui ajoute 42 à ce que vous lui donnez. Mais cette valeur 42 n'est pas codé en dur, il a été spécifié comme argument du constructeur lorsque nous avons créé notre instance foncteur. Je pourrais créer un autre sommateur, qui a ajouté 27, tout en appelant le constructeur avec une valeur différente. Cela les rend bien personnalisable.

Comme les dernières lignes montrent, vous passez souvent foncteurs comme arguments à d'autres fonctions telles que std :: transformer ou les autres algorithmes de la bibliothèque standard. Vous pouvez faire la même chose avec un pointeur de fonction régulière, sauf, comme je l'ai dit plus haut, foncteurs peuvent être « sur mesure », car ils contiennent l'état, ce qui les rend plus souples (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éral, et ajoute tout ce que vous avez initialisé avec), et ils sont potentiellement plus efficaces. Dans l'exemple ci-dessus, le compilateur sait exactement quelle fonction doit appeler std::transform. Il devrait appeler add_x::operator(). Cela signifie qu'il peut inline cet appel de fonction. Et qui le rend aussi efficace que si je l'avais appelé manuellement la fonction de chaque valeur du vecteur.

Si je l'avais passé un pointeur de fonction à la place, le compilateur ne pouvait pas voir immédiatement quelle fonction il pointe, donc à moins qu'il effectue certaines optimisations globales assez complexes, il faudrait à déréférencer le pointeur lors de l'exécution, puis faire la appeler.

Autres conseils

Peu plus. Vous pouvez utiliser boost::function, pour créer foncteurs de fonctions et 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 ajouter é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 :: fonction vous pouvez créer foncteur de méthode de classe, c'est en fait 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 d'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 ce genre de choses, les messages d'erreur du compilateur n'est pas lisible:)

A Functor est un objet qui se comporte 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);

L'avantage réel est qu'un foncteur peut contenir 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)
{ ....}

Comme d'autres l'ont mentionné, un foncteur est un objet qui agit comme une fonction, à savoir surchargeant l'opérateur d'appel de fonction.

Foncteurs sont couramment utilisés dans les algorithmes de STL. Ils sont utiles, car ils peuvent tenir état avant et entre les appels de fonction, comme une fermeture dans les langages fonctionnels. Par exemple, vous pouvez définir un foncteur qui multiplie son MultiplyBy argument un certain montant:

class MultiplyBy {
private:
    int factor;

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

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

Ensuite, vous pouvez passer un objet à un algorithme transform 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 inline dans plus de cas. Si vous avez passé un pointeur de fonction <=>, à moins que que appel a obtenu inline et le compilateur sait que vous passez toujours la même fonction, il ne peut pas inline l'appel via le pointeur.

Pour les débutants comme moi parmi nous. Après une petite recherche je me suis dit ce que le code jalf affiché a fait

Un foncteur est un objet de classe ou struct qui peut être « appelé » comme une fonction. Ceci est rendu possible par une surcharge du () operator. Le + operator (pas sûr de ce que l'appelé) peut prendre un certain nombre d'arguments. D'autres opérateurs ne prennent que deux à savoir le ne peut prendre <=> deux valeurs (un de chaque côté de l'opérateur) et retour quelle que soit la valeur que vous avez surchargé pour. Vous pouvez adapter un certain nombre d'arguments à l'intérieur qui est un <=> ce qui lui donne sa flexibilité.

Pour créer un foncteur d'abord vous créez votre classe. Ensuite, vous créez un constructeur à la classe avec un paramètre de votre choix de type et le nom. Il est suivi dans la même instruction par une liste d'initialisation (qui utilise un seul opérateur du côlon, quelque chose que j'étais aussi nouveau pour) qui construit les objets membres de la classe avec le paramètre précédemment déclaré au constructeur. Ensuite, la surcharge <=>. Enfin, vous déclarez les objets privés de la classe ou struct vous avez créé.

Mon code (je trouve les noms de variables de confusion JALF)

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 mal ne hésitez pas à me corriger!

A foncteur est un fonction d'ordre supérieur qui applique une fonction paramétrée à la ( à savoir) types templated. Il est une généralisation de la carte fonction d'ordre supérieur. Par exemple, nous pourrions définir un foncteur pour comme celui-ci std::vector:

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 et retourne std::vector<T> quand donné une std::vector<U> fonction qui prend une F et retourne un T U. Un foncteur ne doit pas être définie sur les types de conteneurs, il peut être défini pour tout type basé sur un modèle aussi bien, 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)));
}

Voici un exemple simple qui convertit le type à 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 qui foncteurs devraient suivre. La première est la loi d'identité, qui stipule que si le foncteur est donné une fonction d'identité, il devrait être le même que l'application de la fonction d'identité du type, qui est 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 prochaine loi est la loi de composition, qui stipule que si le foncteur est donné une composition de deux fonctions, il devrait être le même que l'application de la foncteur pour la première fonction, puis de nouveau pour la deuxième fonction. Alors, devrait être le fmap(std::bind(f, std::bind(g, _1)), x) 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));

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

J'ai un ensemble de fonctions (par exemple, 20 d'entre eux), et ils sont tous identiques, sauf que chaque appelle une fonction spécifique différente en 3 points spécifiques.

Ceci est incroyable gaspillage et la duplication de code. Normalement, je voudrais juste passer un pointeur de fonction, et il suffit d'appeler que dans les 3 points. (Donc, le code ne doit apparaître qu'une seule fois, au lieu de vingt fois.)

Mais alors je me suis rendu, dans chaque cas, la fonction spécifique nécessaire 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 de substitution dans une classe dérivée. Mais je veux vraiment construire tout cela HÉRITAGE, juste que je peux passer un pointeur de fonction ????

SOLUTION: Alors ce que je faisais était, je fait une classe wrapper (un « Functor ») qui est en mesure d'appeler l'une des fonctions que je avais besoin appelai. Je l'ai mis en place à l'avance (avec ses paramètres, etc.), puis je passe en 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 (je avais besoin pour appeler 3 fois.)


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

.

À l'exception de rappel utilisé, C ++ foncteurs peuvent également aider à fournir un Matlab le style d'accès à un goût matrice classe. Il y a un .

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


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

void* method(void* something)

Vous ne pouvez pas exécuter (d'une manière simple évidente), les méthodes de votre classe dans un fil sans faire quelque chose en plus.

Une très bonne façon de traiter les sujets en C ++, est de créer votre propre classe Thread. Si vous voulez exécuter des méthodes de classe MyClass, ce que je faisais était, transformer ces méthodes en classes dérivées Functor.

En outre, la classe a cette méthode static void* startThread(void* arg):
startThread(..) Un pointeur sur cette méthode sera utilisée comme argument pour appeler void*. Et que devrait recevoir Functor* arg est une référence run() casté en une instance en tas de toute classe dérivée <=>, qui sera casté retour à <=> lorsqu'il est exécuté, puis appelé est la méthode <=>.

Pour ajouter, je l'ai utilisé des objets de fonction pour adapter une méthode héritée existant au modèle de commande; (Seul endroit où la beauté du paradigme OO vrai OCP je me suis senti);  ajoutant également ici le modèle d'adaptateur selon la fonction.

Supposons que votre méthode a la signature:

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

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

.

Note - ce qui est laid, et peut-être vous pouvez utiliser les aides bind 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
};

, nous avons également besoin d'une méthode d'assistance mem_fun3 pour la classe ci-dessus pour aider à appeler.

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));

}

, afin de lier les paramètres, il faut écrire une fonction de liant. Alors, voilà:

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 utiliser avec la classe de commande; utiliser 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 );

Note: f3 (); va appeler la méthode task1-> ThreeParameterTask (21,22,23);.

Le contexte complet de ce modèle au lien suivant

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

Ils sont les plus utiles pour les situations où vous devez associer des données avec des appels répétés ou différés à une fonction.

Par exemple, une liste chaînée de foncteurs pourrait être utilisé pour mettre en œuvre un système de coroutine synchrone à faible surcharge de base, un répartiteur de tâches, ou l'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 utiles qui en eux-mêmes. Ils montrent que la façon dont foncteurs peuvent être utiles, les foncteurs eux-mêmes sont très simples et rigides, ce qui les rend moins utile que, par exemple, ce coup de pouce fournit.

Un grand avantage de mettre en œuvre des fonctions comme foncteurs est qu'ils peuvent maintenir et réutiliser l'état entre les appels. Par exemple, de nombreux algorithmes de programmation dynamique, comme l'algorithme Wagner-Fischer pour le calcul de la Levenshtein entre les chaînes, le travail en remplissant une grande table des résultats. Il est très inefficace d'affecter ce tableau chaque fois que la fonction est appelée, de sorte que la mise en œuvre de la fonction en tant que foncteur et en faisant la table une variable membre peut améliorer considérablement les performances.

Voici un exemple de mise en oeuvre de l'algorithme Wagner-Fischer en tant que foncteur. Remarquez comment la table est allouée dans le constructeur, puis réutilisé dans operator(), avec le redimensionnement 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];
    }
};

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

Mais un foncteur local ne peut pas accéder à des variables automatiques à l'extérieur. La fonction lambda (11 C ++) est une meilleure solution.

Je l'ai « découvert » une utilisation très intéressante de foncteurs: Je les utiliser quand j'ai pas un bon nom pour une méthode, comme foncteur est une méthode sans nom; -)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top