Quali sono i funzioni C ++ e i loro usi?


876

Continuo a sentire molto sui funzioni in C ++. Qualcuno può darmi una panoramica di ciò che sono e in quali casi sarebbero utili?


4
Questo argomento è stato trattato in risposta a questa domanda: stackoverflow.com/questions/317450/why-override-operator#317528
Luc Touraille,

2
È usato per creare una chiusura in C ++.
Copper.hat il

Guardando le risposte di seguito, se qualcuno si chiede cosa operator()(...)significhi: sta sovraccaricando l' operatore di "chiamata di funzione" . È semplicemente un sovraccarico dell'operatore per l' ()operatore. Non confondere operator()con la chiamata di una funzione chiamata operator, ma vederla come la solita sintassi di sovraccarico dell'operatore.
zardosht,

Risposte:


1041

Un functor è praticamente solo una classe che definisce l'operatore (). Ciò ti consente di creare oggetti che "sembrano" una funzione:

// 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

Ci sono un paio di cose carine sui funzione. Uno è che a differenza delle normali funzioni, possono contenere stato. L'esempio sopra crea una funzione che aggiunge 42 a tutto ciò che gli dai. Ma quel valore 42 non è codificato, è stato specificato come argomento del costruttore quando abbiamo creato la nostra istanza di funzione. Potrei creare un altro sommatore, che ha aggiunto 27, semplicemente chiamando il costruttore con un valore diverso. Questo li rende piacevolmente personalizzabili.

Come mostrano le ultime righe, spesso si passano funzioni come argomenti ad altre funzioni come std :: transform o altri algoritmi di libreria standard. Potresti fare lo stesso con un puntatore a funzione normale tranne che, come ho detto sopra, i funzione possono essere "personalizzati" perché contengono stato, rendendoli più flessibili (Se volessi usare un puntatore a funzione, dovrei scrivere una funzione che ha aggiunto esattamente 1 al suo argomento. Il functor è generale e aggiunge qualunque cosa tu l'abbia inizializzato), e sono anche potenzialmente più efficienti. Nell'esempio sopra, il compilatore sa esattamente quale funzione std::transformdeve chiamare. Dovrebbe chiamare add_x::operator(). Ciò significa che può incorporare quella chiamata di funzione. E questo lo rende altrettanto efficace come se avessi chiamato manualmente la funzione su ciascun valore del vettore.

Se invece avessi passato un puntatore a funzione, il compilatore non poteva immediatamente vedere a quale funzione punta, quindi a meno che non eseguisse alcune ottimizzazioni globali abbastanza complesse, avrebbe dovuto dereferenziare il puntatore in fase di esecuzione, quindi effettuare la chiamata.


32
Puoi spiegare questa riga, per favore std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); perché scrivi lì add_x, non l'add42?
Alecs,

102
@Alecs Entrambi avrebbero funzionato (ma l'effetto sarebbe stato diverso). Se avessi usato add42, avrei usato il functor che avevo creato in precedenza e aggiunto 42 a ciascun valore. Con add_x(1)creo una nuova istanza del functor, una che aggiunge solo 1 a ciascun valore. È semplicemente per dimostrare che spesso si crea un'istanza del funzione "al volo", quando ne hai bisogno, anziché crearlo per primo e tenerlo in giro prima di utilizzarlo effettivamente per qualsiasi cosa.
jalf,

8
@zadane ovviamente. Devono solo avere il operator(), perché è quello che il chiamante usa per invocarlo. Che cosa l'ha funtore di funzioni membro, i costruttori, gli operatori e le variabili membro è completamente a voi.
jalf

4
@ rikimaru2013 Nel linguaggio della programmazione funzionale, hai ragione, anche una funzione è un funzione, ma nel linguaggio del C ++, il funzione è specificamente una classe usata come funzione. La terminologia è stata un po 'abusata all'inizio, ma la divisione è utile distinzione e così persiste oggi. Se inizi a fare riferimento a funzioni come "funzioni" in un contesto C ++, confonderai semplicemente la conversazione.
srm,

6
È una classe o un'istanza della classe? Nella maggior parte delle fonti, add42sarebbe chiamato un funzione, non add_x(che è la classe della funzione o solo la classe della funzione). Trovo che la terminologia sia coerente perché i funzione sono anche chiamati oggetti funzione , non classi di funzione. Puoi chiarire questo punto?
Sergei Tachenov,

121

Piccola aggiunta. Puoi usare boost::function, per creare funzioni da funzioni e metodi, come questo:

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"

e puoi usare boost :: bind per aggiungere stato a questo funzione

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"

e molto utile, con boost :: bind e boost :: function puoi creare functor dal metodo class, in realtà questo è un delegato:

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"

È possibile creare un elenco o un vettore di funzioni

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

C'è un problema con tutte queste cose, i messaggi di errore del compilatore non sono leggibili dall'uomo :)


4
Non dovresti operator ()essere pubblico nel tuo primo esempio poiché le classi sono impostate come private?
NathanOliver,

4
forse ad un certo punto questa risposta merita un aggiornamento, dato che ora i lambda sono il modo più semplice per ottenere un funzione da qualsiasi cosa
idclev 463035818

102

Un Functor è un oggetto che agisce come una funzione. Fondamentalmente, una classe che definisce operator().

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

Il vero vantaggio è che un funzione può mantenere lo stato.

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
Devo solo aggiungere che possono essere usati proprio come un puntatore a funzione.
Martin York,

7
@LokiAstari - Per coloro che sono nuovi al concetto, potrebbe essere un po 'fuorviante. I Functor possono essere "usati come", ma non sempre "al posto di" puntatori a funzioni. Ad esempio, una funzione che accetta un puntatore a funzione non può prendere un funzione al suo posto anche se il funzione ha gli stessi argomenti e restituisce il valore del puntatore a funzione. Ma nel complesso durante la progettazione, i funzionali sono la strada preferita e teoricamente "più moderna".
MasonWinsauer,

Perché il secondo ritorna intquando dovrebbe tornare bool? Questo è C ++, non C. Quando è stata scritta questa risposta, boolnon esisteva?
Fondi Monica's Lawsuit

@QPaysTaxes Un errore di battitura credo. Probabilmente ho copiato il codice dal primo esempio e ho dimenticato di cambiarlo. L'ho risolto ora.
James Curran

1
@Riasat Se Matcher è in una libreria, definire Is5 () è abbastanza semplice. E puoi creare Is7 (), Is32 () ecc. Inoltre, questo è solo un esempio. Il funzione potrebbe essere molto più complicata.
James Curran,

51

Il nome "functor" è stato tradizionalmente usato nella teoria delle categorie molto prima che il C ++ apparisse sulla scena. Questo non ha nulla a che fare con il concetto C ++ di functor. È meglio usare il nome dell'oggetto funzione invece di quello che chiamiamo "functor" in C ++. Ecco come altri linguaggi di programmazione chiamano costrutti simili.

Usato al posto della semplice funzione:

Caratteristiche:

  • L'oggetto funzione può avere stato
  • L'oggetto funzione si inserisce in OOP (si comporta come qualsiasi altro oggetto).

Contro:

  • Porta più complessità al programma.

Usato al posto del puntatore a funzione:

Caratteristiche:

  • L'oggetto funzione spesso può essere inline

Contro:

  • L'oggetto funzione non può essere scambiato con un altro tipo di oggetto funzione durante il runtime (almeno a meno che non estenda una classe di base, che quindi fornisce un sovraccarico)

Usato al posto della funzione virtuale:

Caratteristiche:

  • L'oggetto funzione (non virtuale) non richiede il dispacciamento di vtable e runtime, quindi nella maggior parte dei casi è più efficiente

Contro:

  • L'oggetto funzione non può essere scambiato con un altro tipo di oggetto funzione durante il runtime (almeno a meno che non estenda una classe di base, che quindi fornisce un sovraccarico)

1
Puoi spiegare questi casi d'uso in un vero esempio? come possiamo usare i funzioni come polimorfismo e puntatore di funtion?
Milad Khajavi,

1
Cosa significa in realtà che un funzione mantiene lo stato?
erogol

grazie per aver sottolineato che è necessaria una classe base per avere un qualche tipo di polimorfismo. Ho solo il problema che devo usare un functor nello stesso posto di un semplice puntatore a funzione e l'unico modo che ho trovato è stato scrivere una classe base di functor (dato che non posso usare roba C ++ 11). Non ero sicuro che questo sovraccarico abbia senso fino a quando ho letto la tua risposta.
idclev 463035818,

1
@Erogol Un functor è un oggetto che sembra supportare la sintassi foo(arguments). Pertanto, può contenere variabili; per esempio, se avessi una update_password(string)funzione, potresti voler tenere traccia di quanto spesso è successo; con un funzione, che può essere un private long timerappresentante del timestamp che è successo l'ultima volta. Con un puntatore a funzione o una funzione normale, dovresti utilizzare una variabile al di fuori del suo spazio dei nomi, che è direttamente correlata solo dalla documentazione e dall'utilizzo, piuttosto che dalla definizione.l
Fund Monica's Lawsuit

4
For¹ per aver menzionato che il nome è stato inventato senza motivo. Ho appena cercato qual è la relazione tra il matematico (o funzionale se vuoi) funzione e quello del C ++.
Hi-Angel,

41

Come altri hanno già detto, un functor è un oggetto che agisce come una funzione, ovvero sovraccarica l'operatore di chiamata di funzione.

I Functor sono comunemente usati negli algoritmi STL. Sono utili perché possono mantenere lo stato prima e tra le chiamate di funzione, come una chiusura in linguaggi funzionali. Ad esempio, è possibile definire un MultiplyByfunctor che moltiplica il suo argomento per un importo specificato:

class MultiplyBy {
private:
    int factor;

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

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

Quindi potresti passare un MultiplyByoggetto a un algoritmo come 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 altro vantaggio di un funzione rispetto a un puntatore a una funzione è che la chiamata può essere incorporata in più casi. Se hai passato un puntatore a funzione transform, a meno che quella chiamata non sia stata inline e il compilatore sappia che gli passi sempre la stessa funzione, non può incorporare la chiamata attraverso il puntatore.


37

Per i neofiti come me tra noi: dopo una piccola ricerca ho capito cosa faceva il codice pubblicato da jalf.

Un functor è un oggetto di classe o struct che può essere "chiamato" come una funzione. Ciò è reso possibile sovraccaricando il file () operator. Il () operator(non sono sicuro di come si chiamasse) può accettare qualsiasi numero di argomenti. Altri operatori ne prendono solo due, cioè + operatorpossono solo prendere due valori (uno su ciascun lato dell'operatore) e restituire qualunque valore lo abbia sovraccaricato. È possibile inserire qualsiasi numero di argomenti all'interno di un () operatorche è ciò che gli dà la sua flessibilità.

Per creare prima un funzione devi creare la tua classe. Quindi si crea un costruttore per la classe con un parametro a scelta di tipo e nome. Questo è seguito nella stessa dichiarazione da un elenco di inizializzatori (che utilizza un singolo operatore due punti, qualcosa a cui ero anche nuovo) che costruisce gli oggetti membro della classe con il parametro precedentemente dichiarato al costruttore. Quindi il () operatorsovraccarico. Alla fine dichiari gli oggetti privati ​​della classe o struttura che hai creato.

Il mio codice (ho trovato confusi i nomi delle variabili di 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.
}; 

Se qualcosa di questo è inaccurato o semplicemente sbagliato, sentiti libero di correggermi!


1
L'operatore () è chiamato operatore di funzione-chiamata. Immagino che potresti anche chiamarlo operatore tra parentesi.
Gautam,

4
"Questo parametro è in realtà l'argomento" parametroVar "passato dal costruttore che abbiamo appena scritto" Huh?
Razze di leggerezza in orbita

22

Un funzione è una funzione di ordine superiore che applica una funzione ai tipi parametrizzati (ad esempio modelli). È una generalizzazione della funzione di ordine superiore della mappa . Ad esempio, potremmo definire un funzione per std::vectorquesto:

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

Questa funzione accetta a std::vector<T>e restituisce std::vector<U>quando viene assegnata una funzione Fche accetta a Te restituisce a U. Un funzione non deve essere definito su tipi di contenitore, ma può essere definito anche per qualsiasi tipo di modello, tra cui 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)));
}

Ecco un semplice esempio che converte il tipo in 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);

Ci sono due leggi che i funzionari dovrebbero seguire. La prima è la legge sull'identità, che afferma che se al funzione viene assegnata una funzione di identità, dovrebbe essere la stessa applicazione della funzione di identità al tipo, ovvero fmap(identity, x)dovrebbe essere la stessa di 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 legge successiva è la legge sulla composizione, che afferma che se al funzione viene assegnata una composizione di due funzioni, dovrebbe essere la stessa applicazione della funzione per la prima funzione e poi di nuovo per la seconda funzione. Quindi, fmap(std::bind(f, std::bind(g, _1)), x)dovrebbe essere lo stesso di 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
L'articolo sostenendo che functor deve correttamente essere utilizzato per questo significato (vedi anche en.wikipedia.org/wiki/Functor ), e che usarlo per oggetti funzione è solo sciatta: jackieokay.com/2017/01/26/functors.html E ' potrebbe essere troppo tardi per questo, dato il numero di risposte qui che considera solo il significato dell'oggetto funzione.
arm

2
Questa risposta dovrebbe essere quella con> 700 voti positivi. Come qualcuno che conosce Haskell meglio di C ++, la lingua C ++ mi ha lasciato perplesso tutte le volte.
mschmidt,

Teoria delle categorie e C ++? È questo l'account SO segreto di Bartosz Milewski?
Mateen Ulhaq,

1
Potrebbe essere utile riassumere le leggi dei funzioni in notazione standard: fmap(id, x) = id(x)e fmap(f ◦ g, x) = fmap(f, fmap(g, x)).
Mateen Ulhaq,

@mschmidt, anche se functor significa anche questo, C ++ sovraccarica il nome per significare lo stesso di "oggetto funzione"
Caleth,

9

Ecco una situazione reale in cui sono stato costretto a utilizzare un Functor per risolvere il mio problema:

Ho una serie di funzioni (diciamo 20 di esse), e sono tutte identiche, tranne ognuna che chiama una funzione specifica diversa in 3 punti specifici.

Questo è uno spreco incredibile e la duplicazione del codice. Normalmente passerei semplicemente un puntatore a funzione e lo chiamerei solo nei 3 punti. (Quindi il codice deve apparire solo una volta, anziché venti volte.)

Ma poi ho capito, in ogni caso, che la funzione specifica richiedeva un profilo parametri completamente diverso! A volte 2 parametri, a volte 5 parametri, ecc.

Un'altra soluzione sarebbe quella di avere una classe base, in cui la funzione specifica è un metodo ignorato in una classe derivata. Ma voglio davvero costruire tutta questa eredità, solo così posso passare un puntatore a funzione ????

SOLUZIONE: Quindi quello che ho fatto è stato creare una classe wrapper (un "Functor") che è in grado di chiamare una qualsiasi delle funzioni di cui avevo bisogno. L'ho impostato in anticipo (con i suoi parametri, ecc.) E poi lo passo al posto di un puntatore a funzione. Ora il codice chiamato può attivare Functor, senza sapere cosa sta succedendo all'interno. Può persino chiamarlo più volte (ne avevo bisogno per chiamare 3 volte).


Ecco fatto: un esempio pratico in cui un Functor si è rivelato la soluzione ovvia e semplice, che mi ha permesso di ridurre la duplicazione del codice da 20 funzioni a 1.


3
Se il tuo funzione ha chiamato funzioni specifiche diverse e queste altre funzioni variano nel numero di parametri che accettano, significa che il tuo funzione ha accettato un numero variabile di argomenti per l'invio a queste altre funzioni?
johnbakers,

4
puoi per favore spiegare lo scenario sopra citando una parte del codice, sono nuovo di c ++ voglio capire questo concetto ..
sanjeev

3

Ad eccezione di quelli utilizzati nel callback, i funzioni C ++ possono anche aiutare a fornire uno stile di accesso a cui Matlab piace una classe matrix . V'è un esempio .


Questo (l'esempio della matrice) è un semplice uso operator()ma non si avvale delle proprietà dell'oggetto funzione.
Renardesque,

3

Come è stato ripetuto, i funzione sono classi che possono essere trattate come funzioni (operatore di sovraccarico ()).

Sono particolarmente utili per le situazioni in cui è necessario associare alcuni dati a chiamate ripetute o ritardate a una funzione.

Ad esempio, un elenco collegato di funzioni potrebbe essere utilizzato per implementare un sistema coroutine sincrono a basso costo di base, un dispatcher di attività o l'analisi di file interrompibile. Esempi:

/* 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;
}

Naturalmente, questi esempi non sono così utili in se stessi. Mostrano solo in che modo i funzioni possono essere utili, le funzioni stesse sono molto basilari e poco flessibili e ciò le rende meno utili di, ad esempio, ciò che fornisce boost.


2

I Functor sono usati in gtkmm per connettere alcuni pulsanti della GUI a una funzione o metodo C ++ reale.


Se usi la libreria pthread per rendere la tua app multithread, Functors può aiutarti.
Per avviare un thread, uno degli argomenti di pthread_create(..)è il puntatore a funzione da eseguire sul proprio thread.
Ma c'è un inconveniente. Questo puntatore non può essere un puntatore a un metodo, a meno che non sia un metodo statico o se non si specifica la sua classe , come class::method. E un'altra cosa, l'interfaccia del tuo metodo può essere solo:

void* method(void* something)

Quindi non puoi eseguire (in modo semplice e ovvio) metodi della tua classe in un thread senza fare qualcosa in più.

Un ottimo modo per gestire i thread in C ++ è creare la tua Threadclasse. Se volevi eseguire metodi dalla MyClassclasse, quello che ho fatto è stato trasformare questi metodi inFunctor classi derivate.

Inoltre, la Threadclasse ha questo metodo: static void* startThread(void* arg)
un puntatore a questo metodo verrà usato come argomento da chiamare pthread_create(..). E ciò che startThread(..)dovrebbe ricevere in arg è un void*riferimento castato a un'istanza nell'heap di qualsiasi Functorclasse derivata, che verrà ricondotta a Functor*quando eseguita, e quindi chiamata il suo run()metodo.


2

Per aggiungere, ho usato oggetti funzione per adattare un metodo legacy esistente al modello di comando; (solo luogo in cui ho sentito la bellezza del paradigma OO vero OCP); Aggiungendo anche qui il modello dell'adattatore funzione correlato.

Supponiamo che il tuo metodo abbia la firma:

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

Vedremo come adattarlo al modello di comando - per questo, prima di tutto, devi scrivere un adattatore di funzione membro in modo che possa essere chiamato come oggetto funzione.

Nota: questo è brutto e può darsi che tu possa usare gli helper di binding Boost ecc., Ma se non puoi o non vuoi, questo è un modo.

// 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
};

Inoltre, abbiamo bisogno di un metodo di supporto mem_fun3 per la classe sopra per aiutare nella chiamata.

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

}

Ora, al fine di associare i parametri, dobbiamo scrivere una funzione legante. Quindi, ecco qui:

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

E una funzione di supporto per utilizzare 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);
}

Ora, dobbiamo usarlo con la classe Command; usa il seguente typedef:

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

Ecco come lo chiami:

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

Nota: f3 (); chiamerà il metodo task1-> ThreeParameterTask (21,22,23) ;.

Il contesto completo di questo modello al seguente link


2

Un grande vantaggio dell'implementazione di funzioni come funzioni è che possono mantenere e riutilizzare lo stato tra le chiamate. Ad esempio, molti algoritmi di programmazione dinamica, come l' algoritmo di Wagner-Fischer per il calcolo della distanza di Levenshtein tra le stringhe, funzionano compilando una grande tabella di risultati. È molto inefficiente allocare questa tabella ogni volta che viene chiamata la funzione, quindi implementare la funzione come funzione e rendere la tabella una variabile membro può migliorare notevolmente le prestazioni.

Di seguito è riportato un esempio di implementazione dell'algoritmo di Wagner-Fischer come funzione. Notare come la tabella viene allocata nel costruttore e quindi riutilizzata operator(), con il ridimensionamento se necessario.

#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 può anche essere usato per simulare la definizione di una funzione locale all'interno di una funzione. Fare riferimento alla domanda e un'altra .

Ma un funzione locale non può accedere alle variabili automatiche esterne. La funzione lambda (C ++ 11) è una soluzione migliore.


-10

Ho "scoperto" un uso molto interessante dei funzioni: le uso quando non ho un buon nome per un metodo, poiché un funzione è un metodo senza nome ;-)


Perché descrivi un funzione come un "metodo senza nome"?
Anderson Green

5
Una funzione senza un nome è chiamata lambda.
Paul Fultz II,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.