O que é um delegado de C ++?

147

Qual é a ideia geral de um delegado em C ++? O que são, como são usados ​​e para que são usados?

Eu gostaria de aprender sobre eles primeiro de uma forma 'caixa preta', mas um pouco de informação sobre as entranhas dessas coisas também seria ótimo.

Este não é o C ++ mais puro ou limpo, mas noto que a base de código onde trabalho os possui em abundância. Espero entendê-los o suficiente, para que eu possa usá-los e não ter que me aprofundar na horrível horribilidade do modelo aninhado.

Esses dois artigos do The Code Project explicam o que quero dizer, mas não de forma sucinta:

SirYakalot
fonte
4
Você está falando sobre C ++ gerenciado no .NET?
dasblinkenlight
2
Você olhou para a página da Wikipedia para Delegação ?
Matthijs Bierman
5
delegatenão é um nome comum na linguagem c ++. Você deve adicionar algumas informações à pergunta para incluir o contexto em que você a leu. Observe que, embora o padrão possa ser comum, as respostas podem diferir se você falar sobre delegar em geral ou no contexto da CLI do C ++ ou de qualquer outra biblioteca que possua uma implementação específica de delegado .
David Rodríguez - dribeas
6
espera 2 anos?;)
rank1
6
Ainda à espera ...
Grimm O Opiner

Respostas:

173

Você tem um número incrível de opções para obter delegados em C ++. Aqui estão os que me vieram à mente.


Opção 1: functors:

Um objeto de função pode ser criado 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);

Opção 2: expressões lambda (apenas 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);

Opção 3: ponteiros de função

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

Opção 4: ponteiro para funções-membro (solução mais rápida)

Consulte Delegado rápido do C ++ (no 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);

Opção 5: std :: function

(ou boost::functionse sua biblioteca padrão não a suportar). É mais lento, mas é o mais flexível.

#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

Opção 6: ligação (usando std :: bind )

Permite definir alguns parâmetros com antecedência, conveniente para chamar uma função de membro, por exemplo.

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

Opção 7: modelos

Aceite qualquer coisa, desde que corresponda à lista de argumentos.

template <class FunctionT>
int DoSomething(FunctionT func)
{
    return func(3.14);
}
JN
fonte
2
Ótima lista, +1. No entanto, apenas dois realmente contam como delegados aqui - capturando lambdas e o objeto retornado std::bind, e ambos realmente fazem a mesma coisa, exceto que lambdas não são polimórficos no sentido de que podem aceitar diferentes tipos de argumentos.
Xeo 5/03/12
1
@JN: Por que você recomenda não usar ponteiros de função, mas parece estar bem com o uso dos ponteiros de métodos de membros? Eles são apenas idênticos!
Matthieu M.
1
@MatthieuM. : ponto justo. Eu estava considerando os indicadores de função como legados, mas provavelmente esse é apenas o meu gosto pessoal.
JN
1
@ Xeo: minha ideia de delegado é bastante empírica. Talvez eu esteja misturando muito objeto de função e delegado (culpe minha experiência anterior em C #).
JN
2
@SirYakalot Algo que se comporta como uma função, mas que pode conter o estado ao mesmo tempo e pode ser manipulado como qualquer outra variável. Um uso, por exemplo, é pegar uma função que usa dois parâmetros e forçar o 1º parâmetro a ter determinado valor, criando uma nova função com um único parâmetro ( bindingna minha lista). Você não pode conseguir isso com ponteiros de função.
JN
37

Um delegado é uma classe que envolve um ponteiro ou referência a uma instância do objeto, um método membro da classe desse objeto a ser chamado nessa instância do objeto e fornece um método para acionar essa chamada.

Aqui está um exemplo:

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
fonte
que fornece um método para acionar a chamada? o delegado? como? ponteiro de função?
SirYakalot
A classe delegada oferecerá um método como o Execute()que aciona a chamada de função no objeto que o delegado envolve.
Grimm The Opiner
4
Em vez de Executar, eu recomendo no seu caso substituir o operador de chamada - void CCallback :: operator () (int). A razão para isso está na programação genérica, espera-se que objetos chamados sejam chamados como uma função. o.Execute (5) seria incompatível, mas o (5) se encaixaria perfeitamente como um argumento de modelo que pode ser chamado. Essa classe também pode ser generalizada, mas presumo que você tenha mantido a simplicidade por brevidade (o que é uma coisa boa). Fora esse nitpick, esta é uma classe muito útil.
David Peterson
17

A necessidade de implementações de delegado em C ++ é um constrangimento duradouro para a comunidade C ++. Todos os programadores de C ++ gostariam de tê-los, portanto, eles eventualmente os usam, apesar dos fatos de que:

  1. std::function() usa operações de heap (e está fora de alcance para programação incorporada séria).

  2. Todas as outras implementações fazem concessões em relação à portabilidade ou conformidade padrão em graus maiores ou menores (verifique por meio da inspeção das várias implementações delegadas aqui e no projeto de código). Ainda estou para ver uma implementação que não usa wild reinterpret_casts, classe aninhada "protótipos" que, esperançosamente, produzem ponteiros de função do mesmo tamanho que o passado pelo usuário, truques do compilador como o primeiro a declarar para a frente e, em seguida, typedef declara novamente, desta vez herdando de outra classe ou técnicas obscuras semelhantes. Embora seja uma grande conquista para os implementadores que construíram isso, ainda é um triste testemunho de como o C ++ evolui.

  3. Raramente é apontado que, agora com mais de 3 revisões padrão do C ++, os delegados não foram abordados adequadamente. (Ou a falta de recursos de idioma que permitem implementações simples de delegado.)

  4. Com a maneira como as funções lambda do C ++ 11 são definidas pelo padrão (cada lambda possui um tipo diferente e anônimo), a situação só melhorou em alguns casos de uso. Mas, para o caso de uso do uso de delegados nas APIs da biblioteca (DLL), somente lambdas ainda não são utilizáveis. A técnica comum aqui é primeiro empacotar o lambda em uma função std :: e depois passá-lo pela API.

BitTickler
fonte
Eu fiz uma versão dos retornos de chamada genéricos de Elbert Mai no C ++ 11 com base em outras idéias vistas em outros lugares, e parece esclarecer a maioria dos problemas citados em 2). O único problema persistente restante para mim é que estou preso usando uma macro no código do cliente para criar os delegados. Provavelmente existe uma maneira mágica de sair disso que ainda não resolvi.
kert
3
Eu estou usando std :: function sem heap no sistema incorporado e ainda funciona.
Prasad #
1
std::functionnão sempre alocar dinamicamente
Leveza raças em Orbit
3
Esta resposta é mais do que uma resposta real
Lightness Races in Orbit
7

Muito simplesmente, um delegado fornece funcionalidade de como um ponteiro de função DEVE trabalhar. Existem muitas limitações de ponteiros de função no C ++. Um delegado usa alguma desagradabilidade de modelo nos bastidores para criar uma coisa de tipo de ponteiro de função da classe de modelo que funciona da maneira que você deseja.

ie - você pode configurá-los para apontar para uma determinada função e pode distribuí-los e chamá-los quando e onde quiser.

Existem alguns exemplos muito bons aqui:

SirYakalot
fonte
4

Uma opção para delegados em C ++ que não é mencionado aqui é fazê-lo no estilo C usando uma função ptr e um argumento de contexto. Esse é provavelmente o mesmo padrão que muitos que estão fazendo essa pergunta estão tentando evitar. Mas, o padrão é portátil, eficiente e utilizável em código incorporado e de 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
fonte
1

Windows Runtime equivalente a um objeto de função no C ++ padrão. Pode-se usar toda a função como parâmetro (na verdade, esse é um ponteiro de função). É usado principalmente em conjunto com eventos. O delegado representa um contrato que os manipuladores de eventos cumprem muito. Facilita como um ponteiro de função pode funcionar.

nmserve
fonte