¿Qué es un delegado de C ++?

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

Me gustaría aprender sobre ellos en una forma de ‘caja negra’, pero un poco de información sobre las agallas 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 comprenderlos lo suficiente, así que puedo usarlos y no tener que ahondar en el espantoso horror de la plantilla anidada.

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

  • Punteros de función de miembro y los delegates de C ++ más rápidos posibles

  • Los delegates Imposibly Fast C ++

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


Opción 1: funtores:

Se puede crear un objeto de función implementando el 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 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 :: función

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

 #include  std::function 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 configurar algunos parámetros de antemano, conveniente para llamar a una función miembro, por ejemplo.

 struct MyClass { int DoStuff(double d); // actually a DoStuff(MyClass* this, double d) }; std::function 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  int DoSomething(FunctionT func) { return func(3.14); } 

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 a ser llamado en esa instancia de objeto, y proporciona un método para activar esa llamada.

Aquí hay un ejemplo:

 template  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 cbk( a, &A::Fn ); cbk.Execute( 3 ); } 

La necesidad de implementaciones de delegates C ++ es una vergüenza duradera para la comunidad C ++. A cada progtwigdor de C ++ le encantaría tenerlos, por lo que eventualmente los usará a pesar de los hechos que:

  1. std::function() usa operaciones de montón (y está fuera del scope de la progtwigción embebida 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 delegates 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 comstackció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. Solo en raras ocasiones se señala que ahora más de 3 revisiones estándar de C ++, los delegates no fueron abordados adecuadamente. (O la falta de características de idioma que permiten implementaciones directas de delegates).

  4. Con la forma en que las funciones de C ++ 11 lambda están definidas por el estándar (cada lambda tiene un tipo diferente y anónimo), la situación solo ha mejorado en algunos casos de uso. Pero para el caso de uso del uso de delegates en las API de biblioteca (DLL), las lambdas por sí solas aún 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.

Muy simplemente, un delegado proporciona la funcionalidad de cómo DEBERÍA 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.

Aquí hay algunos muy buenos ejemplos:

Una opción para delegates en C ++ que no se menciona aquí es hacerlo 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); ... 

Equivalente de Windows Runtime 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.