Che cos'è un delegato C ++?


147

Qual è l'idea generale di un delegato in C ++? Cosa sono, come vengono utilizzati e a cosa servono?

Mi piacerebbe prima conoscerli in un modo "scatola nera", ma anche un po 'di informazioni sull'intestino di queste cose sarebbe eccezionale.

Questo non è C ++ nella sua forma più pura o pulita, ma noto che la base di codice in cui lavoro li ha in abbondanza. Spero di capirli abbastanza, quindi posso semplicemente usarli e non dover approfondire l'orribile orribile modello annidato.

Questi due articoli di The Code Project spiegano cosa intendo, ma non in modo particolarmente succinto:


4
Stai parlando di C ++ gestito sotto .NET?
dasblinkenlight


5
delegatenon è un nome comune in linguaggio parlamentare c ++. È necessario aggiungere alcune informazioni alla domanda per includere il contesto in cui è stata letta. Si noti che sebbene il modello possa essere comune, le risposte potrebbero essere diverse se si parla di delegato in generale o nel contesto della CLI C ++ o di qualsiasi altra libreria che ha un'implementazione particolare di delegato .
David Rodríguez - dribeas,

6
aspettare 2 anni?;)
rank1

6
Sto

Risposte:


173

Hai un numero incredibile di scelte per raggiungere i delegati in C ++. Ecco quelli che mi sono venuti in mente.


Opzione 1: funzioni:

Un oggetto funzione può essere creato implementando operator()

struct Functor
{
     // Normal class/struct members

     int operator()(double d) // Arbitrary return types and parameter list
     {
          return (int) d + 1;
     }
};

// Use:
Functor f;
int i = f(3.14);

Opzione 2: espressioni lambda (solo C ++ 11 )

// Syntax is roughly: [capture](parameter list) -> return type {block}
// Some shortcuts exist
auto func = [](int i) -> double { return 2*i/1.15; };
double d = func(1);

Opzione 3: puntatori a funzione

int f(double d) { ... }
typedef int (*MyFuncT) (double d);
MyFuncT fp = &f;
int a = fp(3.14);

Opzione 4: puntatore alle funzioni membro (soluzione più veloce)

Vedi Delegato C ++ veloce (su The Code Project ).

struct DelegateList
{
     int f1(double d) { }
     int f2(double d) { }
};

typedef int (DelegateList::* DelegateType)(double d);

DelegateType d = &DelegateList::f1;
DelegateList list;
int a = (list.*d)(3.14);

Opzione 5: std :: function

(o boost::functionse la tua libreria standard non la supporta). È più lento, ma è il più flessibile.

#include <functional>
std::function<int(double)> f = [can be set to about anything in this answer]
// Usually more useful as a parameter to another functions

Opzione 6: associazione (usando std :: bind )

Consente di impostare alcuni parametri in anticipo, ad esempio è utile chiamare una funzione membro.

struct MyClass
{
    int DoStuff(double d); // actually a DoStuff(MyClass* this, double d)
};

std::function<int(double d)> f = std::bind(&MyClass::DoStuff, this, std::placeholders::_1);
// auto f = std::bind(...); in C++11

Opzione 7: modelli

Accetta qualsiasi cosa purché corrisponda all'elenco degli argomenti.

template <class FunctionT>
int DoSomething(FunctionT func)
{
    return func(3.14);
}

2
Grande elenco, +1. Tuttavia, solo due contano davvero come delegati qui: catturare lambda e l'oggetto restituito std::bind, ed entrambi fanno davvero la stessa cosa, tranne che i lambda non sono polimorfici, nel senso che possono accettare diversi tipi di argomenti.
Xeo

1
@JN: Perché mi consiglia di non utilizzare i puntatori a funzione ma sembra che stia bene con i puntatori sui metodi membro? Sono identici!
Matthieu M.

1
@MatthieuM. : punto valido. Stavo considerando i puntatori a funzione come eredità, ma probabilmente è solo un mio gusto personale.
JN,

1
@Xeo: la mia idea di delegato è piuttosto empirica. Forse sto mescolando troppi oggetti funzione e delegato (biasimo la mia precedente esperienza in C #).
JN,

2
@SirYakalot Qualcosa che si comporta come una funzione, ma che può mantenere lo stato allo stesso tempo e può essere manipolato come qualsiasi altra variabile. Un uso, ad esempio, è prendere una funzione che accetta due parametri e forzare il primo parametro ad avere un certo valore creando una nuova funzione con un singolo parametro ( bindingnella mia lista). Non puoi farlo con i puntatori a funzione.
JN,

37

Un delegato è una classe che avvolge un puntatore o un riferimento a un'istanza di oggetto, un metodo membro della classe di quell'oggetto da chiamare su quell'istanza di oggetto e fornisce un metodo per attivare quella chiamata.

Ecco un esempio:

template <class T>
class CCallback
{
public:
    typedef void (T::*fn)( int anArg );

    CCallback(T& trg, fn op)
        : m_rTarget(trg)
        , m_Operation(op)
    {
    }

    void Execute( int in )
    {
        (m_rTarget.*m_Operation)( in );
    }

private:

    CCallback();
    CCallback( const CCallback& );

    T& m_rTarget;
    fn m_Operation;

};

class A
{
public:
    virtual void Fn( int i )
    {
    }
};


int main( int /*argc*/, char * /*argv*/ )
{
    A a;
    CCallback<A> cbk( a, &A::Fn );
    cbk.Execute( 3 );
}

quale fornisce un metodo per attivare la chiamata? il delegato? Come? puntatore a funzione?
SirYakalot,

La classe delegata offrirà un metodo come quello Execute()che attiva la chiamata di funzione sull'oggetto che il delegato avvolge.
Grimm The Opiner

4
Invece di Execute, nel tuo caso consiglio vivamente di sostituire l'operatore di chiamata - void CCallback :: operator () (int). Il motivo è nella programmazione generica, si prevede che gli oggetti richiamabili siano chiamati come una funzione. o.Execute (5) sarebbe incompatibile, ma o (5) si adatterebbe bene come argomento modello richiamabile. Questa classe potrebbe anche essere generalizzata, ma suppongo che tu l'abbia reso semplice per brevità (il che è una buona cosa). Oltre a questo nitpick, questa è una classe molto utile.
David Peterson,

18

La necessità di implementazioni di delegati C ++ è un imbarazzo di lunga durata per la comunità C ++. Ogni programmatore C ++ vorrebbe averli, quindi alla fine li usano nonostante i fatti che:

  1. std::function() utilizza le operazioni heap (ed è fuori portata per una seria programmazione integrata).

  2. Tutte le altre implementazioni fanno concessioni verso la portabilità o la conformità standard a livelli maggiori o minori (si prega di verificare ispezionando le varie implementazioni dei delegati qui e su codeproject). Devo ancora vedere un'implementazione che non utilizza reinterpret_casts selvaggi, "prototipi" di classe nidificati che si spera producano puntatori a funzioni della stessa dimensione di quelli passati dall'utente, trucchi del compilatore come prima dichiarazione in avanti, quindi digitazione e poi dichiarazione di nuovo, questa volta ereditando da un'altra classe o simili tecniche ombrose. Sebbene sia un grande risultato per gli implementatori che l'hanno costruito, è ancora una triste testimonianza dell'evoluzione del C ++.

  3. Solo raramente viene sottolineato che ora oltre 3 revisioni C ++ standard, i delegati non venivano indirizzati correttamente. (O la mancanza di funzionalità linguistiche che consentono implementazioni dirette dei delegati.)

  4. Con il modo in cui le funzioni lambda C ++ 11 sono definite dallo standard (ogni lambda ha un tipo anonimo e diverso), la situazione è migliorata solo in alcuni casi d'uso. Ma per il caso d'uso dell'uso dei delegati nelle API della libreria (DLL), i lambda da soli non sono ancora utilizzabili. La tecnica comune qui è prima impacchettare lambda in una funzione std :: e poi passarlo attraverso l'API.


Ho fatto una versione dei callback generici di Elbert Mai in C ++ 11 basandomi su altre idee viste altrove, e sembra chiarire la maggior parte dei problemi citati in 2). L'unico problema di fastidio residuo per me è che sono bloccato usando una macro nel codice client per creare i delegati. C'è probabilmente un modo magico per uscire da questo che non ho ancora risolto.
kert

3
Sto usando std :: function senza heap nel sistema incorporato e funziona ancora.
prasad,

1
std::functionnon sempre alloca dinamicamente
Razze di leggerezza in orbita il

3
Questa risposta è molto più rantesca di una vera risposta
gare di leggerezza in orbita il

8

Molto semplicemente, un delegato fornisce funzionalità per il funzionamento di un puntatore a funzione. Esistono molte limitazioni dei puntatori a funzione in C ++. Un delegato usa un po 'di cattiveria dietro le quinte per creare una funzione-puntatore-tipo-classe di classe modello che funziona nel modo desiderato.

vale a dire: puoi impostarli in modo che puntino a una determinata funzione e puoi passarli in giro e chiamarli quando e dove vuoi.

Ci sono alcuni ottimi esempi qui:


4

Un'opzione per i delegati in C ++ che non è altrimenti menzionata qui è quella di farlo in stile C usando una funzione ptr e un argomento di contesto. Questo è probabilmente lo stesso modello che molti che fanno questa domanda stanno cercando di evitare. Tuttavia, il modello è portatile, efficiente ed è utilizzabile nel codice incorporato e nel kernel.

class SomeClass
{
    in someMember;
    int SomeFunc( int);

    static void EventFunc( void* this__, int a, int b, int c)
    {
        SomeClass* this_ = static_cast< SomeClass*>( this__);

        this_->SomeFunc( a );
        this_->someMember = b + c;
    }
};

void ScheduleEvent( void (*delegateFunc)( void*, int, int, int), void* delegateContext);

    ...
    SomeClass* someObject = new SomeObject();
    ...
    ScheduleEvent( SomeClass::EventFunc, someObject);
    ...

1

Equivalente di Windows Runtime di un oggetto funzione in C ++ standard. Si può usare l'intera funzione come parametro (in realtà si tratta di un puntatore a funzione). Viene utilizzato principalmente in combinazione con eventi. Il delegato rappresenta un contratto che i gestori di eventi adempiono molto. Facilita come può funzionare un puntatore a funzione.

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.