2012-03-05 26 views
109

¿Cuál es la idea general de un delegado en C++? ¿Qué son, cómo se usan y para qué se utilizan?¿Qué es un delegado de C++?

Me gustaría aprender acerca de ellos en una forma de 'caja negra', pero un poco de información sobre las agallas de estas cosas sería genial también.

Esto no es C++ en su estado más puro o más limpio, pero me doy cuenta de que la base de código donde trabajo los tiene en abundancia. Espero comprenderlos lo suficiente, así que puedo usarlos y no tener que ahondar en el espantoso horror de la plantilla anidada.

Estos dos The Code Project artículos de explicar lo que quiero decir pero no particularmente de manera sucinta:

+3

¿Estás hablando de C++ administrado en .NET? – dasblinkenlight

+2

¿Miraste la [página de Wikipedia para la Delegación] (http://en.wikipedia.org/wiki/Delegation_%28programming%29)? –

+3

'delegado' no es un nombre común en lenguaje C++. Debe agregar cierta información a la pregunta para incluir el contexto en el que la ha leído. Tenga en cuenta que si bien el patrón puede ser común, las respuestas pueden diferir si habla de * delegate * en general, o en el contexto de la CLI de C++ o cualquier otra biblioteca que tenga una implementación particular de * delegate *. –

Respuesta

132

Usted tiene un increíble número de opciones para lograr los delegados en C++. Aquí están los que me vinieron a la mente.


Opción 1: funtores:

Un objeto de función puede ser creado por la aplicación de 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); 

Opción 2: expresiones lambda (C++11 solamente)

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

Opción 3: punteros de función

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

Opción 4: puntero a funciones miembro (solución más rápida)

Ver Fast C++ Delegate (en 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); 

Opción 5: std::function

(o boost::function si su biblioteca estándar no lo soporta). Es más lento, pero es el más flexible.

#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 

Opción 6: unión (utilizando std::bind)

permite configurar algunos parámetros de antelación, convenientes para llamar a una función miembro, por ejemplo.

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 

Opción 7: Plantillas

aceptar cualquier cosa con tal de que coincide con la lista de argumentos.

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

Gran lista, +1. Sin embargo, solo dos realmente cuentan como delegados aquí, capturando lambdas y el objeto devuelto por 'std :: bind', y ambos realmente hacen lo mismo, excepto que las lambdas no son polimórficas en el sentido de que pueden aceptar diferentes tipos de argumentos. – Xeo

+1

@ J.N: ¿Por qué recomienda no utilizar punteros a las funciones pero parece estar bien con el uso de los punteros de los métodos miembro? ¡Son simplemente idénticos! –

+1

@MatthieuM. : punto justo. Estaba considerando punteros de función para ser legado, pero eso es probablemente solo mi gusto personal. –

29

Un delegado es una clase que se ajusta un puntero o referencia a una instancia de objeto, a un método miembro de la clase de ese objeto a ser llamado en esa instancia de objeto, y proporciona un método para activar esa llamada.

He aquí un ejemplo:

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); 
} 
+0

que proporciona un método para activar la llamada? el delegado? ¿cómo? puntero de función? – SirYakalot

+0

La clase de delegado ofrecerá un método como 'Execute()' que activa la llamada de función en el objeto que el delegado ajusta. –

+2

En lugar de Execute, recomiendo encarecidamente en su caso anular el operador de llamada - void CCallback :: operator() (int). La razón para esto está en la programación genérica, se espera que los objetos invocables se llamen como una función. o.Execute (5) sería incompatible, pero o (5) se ajustaría muy bien como un argumento de plantilla invocable. Esta clase también podría ser generalizada, pero supongo que lo mantuviste simple por brevedad (lo cual es algo bueno). Aparte de eso, esta es una clase muy útil. –

5

Muy simple, un delegado proporciona la funcionalidad de cómo DEBE funcionar un puntero a la función. Hay muchas limitaciones de indicadores de función en C++. Un delegado utiliza algunos desaciertos de la plantilla detrás de las cámaras para crear una función de tipo plantilla-puntero-tipo-cosa que funciona de la manera que le gustaría.

es decir, puede configurarlos para que apunten a una función determinada y puede pasarlos y llamarlos cuando y donde quiera.

Hay algunos muy buenos ejemplos aquí:

14

La necesidad de C++ implementaciones de delegados son mucho vergüenza duradera al ++ comunidad C. Cada programador de C++ le gustaría tener ellos, por lo que finalmente usarlas a pesar del hecho de que:

  1. std::function() utiliza operaciones de lixiviación (y está fuera del alcance de programación integrado en serio).

  2. Todas las demás implementaciones hacen concesiones hacia la portabilidad o la conformidad estándar en mayor o menor grado (compruebe inspeccionando las diversas implementaciones de delegados aquí y en el proyecto de código). Aún no he visto una implementación que no use wild reinterpret_casts, clase anidada "prototipos" que con suerte produzcan punteros de función del mismo tamaño que el que pasó el usuario, trucos de compilación como first forward declare, luego typedef y declare nuevamente, esta vez hereda de otra clase o técnicas similares con sombra. Si bien es un gran logro para los implementadores que crearon eso, sigue siendo una triste prueba de cómo evoluciona C++.

  3. Rara vez se señala, que ahora más de 3 revisiones estándar de C++, los delegados no fueron atendidos correctamente. (O la falta de características de idioma que permiten implementaciones de delegado directas.)

  4. Con la forma en que las funciones C++ 11 lambda están definidas por el estándar (cada lambda tiene un tipo anónimo diferente), la situación solo ha mejorado en algunos casos de uso. Pero para el caso de uso del uso de delegados en las API de la biblioteca (DLL), lambdas solo todavía no se pueden usar. La técnica común aquí es primero empaquetar el lambda en una función std :: y luego pasarlo a través de la API.

+0

gracias por decir que –

+0

He hecho una versión de las devoluciones de llamada genéricas de Elbert Mai en C++ 11 basadas en otras ideas vistas en otros lugares, y parece que borra la mayoría de los problemas que citas en 2). El único problema persistente para mí es que estoy atrapado usando una macro en el código del cliente para crear los delegados. Probablemente hay una forma mágica de salir de esto que aún no he resuelto. – kert

+1

Estoy usando std :: function sin heap en el sistema integrado y todavía funciona. – prasad

1

Windows Tiempo de ejecución equivalente de un objeto de función en C++ estándar. Uno puede usar la función completa como un parámetro (en realidad, es un puntero de función). Se usa principalmente en conjunción con eventos. El delegado representa un contrato que los controladores de eventos cumplen mucho. Facilita el funcionamiento de un puntero a función.

2

Una opción para los delegados en C++ que no se menciona aquí es hacer el estilo C usando una función ptr y un argumento de contexto. Este es probablemente el mismo patrón que muchos que están haciendo esta pregunta intentan evitar. Sin embargo, el patrón es portátil, eficiente y utilizable en código incrustado y 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); 
    ... 
Cuestiones relacionadas