¿Qué es un delegado de C ++?

147

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

Primero me gustaría aprender sobre ellos en forma de 'caja negra', pero un poco de información sobre las entrañas de estas cosas también sería genial.

Esto no es C ++ en su forma más pura o más limpia, pero noto que la base de código donde trabajo los tiene en abundancia. Espero entenderlos lo suficiente, para poder usarlos y no tener que profundizar en el horrible horror de la plantilla anidada.

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

Sir Yakalot
fuente
44
¿Estás hablando de C ++ administrado bajo .NET?
dasblinkenlight
2
¿Viste la página de Wikipedia para Delegación ?
Matthijs Bierman
55
delegateno es un nombre común en lenguaje c ++. Debe agregar 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 delegado en general, o en el contexto de C ++ CLI o cualquier otra biblioteca que tenga una implementación particular de delegado .
David Rodríguez - dribeas
66
esperar 2 años?;)
rango1
66
Todavía
estoy

Respuestas:

173

Tiene una cantidad increíble de opciones para lograr delegados en C ++. Aquí están los que me vinieron a la mente.


Opción 1: functores:

Se puede crear un objeto de función 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);

Opción 2: expresiones 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);

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 Delegado Fast C ++ (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::functionsi su biblioteca estándar no lo admite). 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: enlace (usando std :: bind )

Permite establecer algunos parámetros de antemano, conveniente 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

Acepte cualquier cosa siempre que coincida con la lista de argumentos.

template <class FunctionT>
int DoSomething(FunctionT func)
{
    return func(3.14);
}
JN
fuente
2
Gran lista, +1. Sin embargo, solo dos realmente cuentan como delegados aquí: capturar lambdas y el objeto devuelto 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
@JN: ¿Por qué recomienda no usar punteros de función pero parece estar bien con el uso de punteros de métodos de miembros? ¡Son simplemente idénticos!
Matthieu M.
1
@MatthieuM. : punto justo. Estaba considerando los punteros de funciones como heredados, pero probablemente ese sea solo mi gusto personal.
JN
1
@Xeo: mi idea de un delegado es bastante empírica. Tal vez estoy mezclando demasiado objeto de función y delegado (culpo a mi experiencia anterior de C #).
JN
2
@SirYakalot Algo que se comporta como una función, pero que puede mantener el estado al mismo tiempo y puede manipularse como cualquier otra variable. Un uso, por ejemplo, es tomar una función que tome dos parámetros y obligar al primer parámetro a tener cierto valor creando una nueva función con un solo parámetro ( bindingen mi lista). No puede lograr esto con punteros de función.
JN
37

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

Aquí hay 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 );
}
Grimm The Opiner
fuente
que proporciona un método para activar la llamada? el delegado? ¿cómo? puntero de función?
SirYakalot
La clase delegado ofrecerá un método como el Execute()que activa la llamada de función en el objeto que el delegado envuelve.
Grimm The Opiner
44
En lugar de Ejecutar, recomendaría en su caso anular el operador de llamada - void CCallback :: operator () (int). La razón de esto es en la programación genérica, se espera que los objetos invocables se llamen como una función. o.Ejecute (5) sería incompatible, pero o (5) encajaría bien como un argumento de plantilla invocable. Esta clase también podría generalizarse, pero supongo que la mantuviste simple por brevedad (lo cual es algo bueno). Aparte de eso, esta es una clase muy útil.
David Peterson
18

La necesidad de implementaciones de delegados de C ++ es una vergüenza duradera para la comunidad de C ++. A todos los programadores de C ++ les encantaría tenerlos, por lo que eventualmente los usan a pesar de los hechos que:

  1. std::function() utiliza operaciones de almacenamiento dinámico (y está fuera del alcance de la programación integrada seria).

  2. Todas las demás implementaciones hacen concesiones hacia la portabilidad o la conformidad estándar en mayor o menor grado (verifique inspeccionando las diversas implementaciones de delegados aquí y en el proyecto de código). Todavía tengo que ver una implementación que no use wild reinterpret_casts, "prototipos" de clase anidada que con suerte producen punteros de función del mismo tamaño que el que pasó el usuario, trucos del compilador como primero declarar hacia adelante, luego escribirdef y luego declarar nuevamente, esta vez heredando de otra clase o técnicas de sombra similares. Si bien es un gran logro para los implementadores que lo crearon, sigue siendo un triste testimonio de cómo evoluciona C ++.

  3. Solo en raras ocasiones se señala que ahora, con más de 3 revisiones estándar de C ++, los delegados no fueron abordados adecuadamente. (O la falta de características de lenguaje que permitan implementaciones directas de delegados).

  4. Con la forma en que las funciones lambda de C ++ 11 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 de delegados en las API de bibliotecas (DLL), las lambdas por sí solas aún no son utilizables. La técnica común aquí es empaquetar primero el lambda en una función std :: y luego pasarlo a través de la API.

BitTickler
fuente
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 aclarar la mayoría de los problemas que cita 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 haya una salida mágica de plantilla que aún no he resuelto.
kert
3
Estoy usando std :: function sin montón en el sistema incrustado y todavía funciona.
prasad
1
std::functionno siempre se asigna dinámicamente
carreras de ligereza en órbita el
3
Esta respuesta es más una queja que una respuesta real
carreras de ligereza en órbita el
8

Muy simple, un delegado proporciona funcionalidad sobre cómo DEBE funcionar un puntero de función. Hay muchas limitaciones de los punteros de función en C ++. Un delegado usa algo de desagradable plantilla detrás de escena para crear una función de tipo de puntero de función de clase de plantilla que funcione de la manera que desee.

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

Aquí hay algunos muy buenos ejemplos:

Sir Yakalot
fuente
4

Una opción para delegados en C ++ que no se menciona aquí es hacerlo al estilo C usando una función ptr y un argumento de contexto. Este es probablemente el mismo patrón que muchas personas que hacen esta pregunta intentan evitar. Pero, el patrón es portátil, eficiente y se puede usar 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);
    ...
Joe
fuente
1

Windows Runtime equivalente de un objeto de función en C ++ estándar. Se puede usar toda la función como parámetro (en realidad, es un puntero de función). Se utiliza principalmente en conjunción con eventos. El delegado representa un contrato que los manejadores de eventos cumplen mucho. Facilita cómo puede funcionar un puntero de función.

nmserve
fuente