No C ++, quando e como você usa uma função de retorno de chamada?
EDIT:
Gostaria de ver um exemplo simples para escrever uma função de retorno de chamada.
No C ++, quando e como você usa uma função de retorno de chamada?
EDIT:
Gostaria de ver um exemplo simples para escrever uma função de retorno de chamada.
Respostas:
Nota: A maioria das respostas abrange indicadores de função, que é uma possibilidade de obter lógica de "retorno de chamada" em C ++, mas atualmente não é a mais favorável que eu acho.
O que são retornos de chamada (?) E por que usá-los (!)
A chamada de retorno é um exigível (ver mais abaixo) aceita por uma classe ou função, usado para personalizar a lógica atual, dependendo de que callback.
Um motivo para usar retornos de chamada é escrever um código genérico independente da lógica na função chamada e pode ser reutilizado com retornos de chamada diferentes.
Muitas funções da biblioteca de algoritmos padrão
<algorithm>
usam retornos de chamada. Por exemplo, ofor_each
algoritmo aplica um retorno de chamada unário a cada item em um intervalo de iteradores:que pode ser usado para incrementar primeiro e, em seguida, imprimir um vetor passando callables apropriados, por exemplo:
que imprime
Outra aplicação de retornos de chamada é a notificação de chamadores de certos eventos, o que permite uma certa flexibilidade de estática / tempo de compilação.
Pessoalmente, uso uma biblioteca de otimização local que usa dois retornos de chamada diferentes:
Portanto, o designer da biblioteca não é responsável por decidir o que acontece com as informações que são fornecidas ao programador por meio do retorno de chamada de notificação e ele não precisa se preocupar em como realmente determinar os valores das funções, pois eles são fornecidos pelo retorno de chamada lógica. Consertar essas coisas é uma tarefa devido ao usuário da biblioteca e mantém a biblioteca pequena e mais genérica.
Além disso, os retornos de chamada podem ativar o comportamento dinâmico do tempo de execução.
Imagine algum tipo de classe de mecanismo de jogo que tenha uma função acionada, toda vez que os usuários pressionam um botão no teclado e um conjunto de funções que controlam o comportamento do jogo. Com retornos de chamada, você pode (re) decidir em tempo de execução qual ação será executada.
Aqui, a função
key_pressed
usa os retornos de chamada armazenadosactions
para obter o comportamento desejado quando uma certa tecla é pressionada. Se o jogador optar por mudar o botão para pular, o mecanismo poderá chamare, assim, altere o comportamento de uma chamada para
key_pressed
( para a qual as chamadasplayer_jump
) quando este botão for pressionado na próxima vez no jogo.O que são callables em C ++ (11)?
Consulte Conceitos do C ++: Callable on cppreference para uma descrição mais formal.
A funcionalidade de retorno de chamada pode ser realizada de várias maneiras no C ++ (11), pois várias coisas diferentes podem ser chamadas * :
std::function
objetosoperator()
)* Nota: O ponteiro para os membros dos dados também pode ser chamado, mas nenhuma função é chamada.
Várias maneiras importantes de escrever retornos de chamada em detalhes
Nota: A partir do C ++ 17, uma chamada como
f(...)
pode ser escrita, poisstd::invoke(f, ...)
também lida com o ponteiro para o caso do membro.1. Indicadores de função
Um ponteiro de função é o tipo mais simples (em termos de generalidade; em termos de legibilidade, sem dúvida o pior) que um retorno de chamada pode ter.
Vamos ter uma função simples
foo
:1.1 Escrevendo um apontador de função / notação de tipo
Um tipo de ponteiro de função possui a notação
onde um tipo de ponteiro de função nomeado será semelhante
A
using
declaração nos dá a opção de tornar as coisas um pouco mais legíveis, pois otypedef
forf_int_t
também pode ser escrito como:Onde (pelo menos para mim) fica mais claro que
f_int_t
é o novo alias de tipo e o reconhecimento do tipo de ponteiro de função também é mais fácilE uma declaração de uma função usando um retorno de chamada do tipo de ponteiro de função será:
1.2 Notação de chamada de retorno
A notação de chamada segue a sintaxe da chamada de função simples:
1.3 Retorno de chamada usa notação e tipos compatíveis
Uma função de retorno de chamada usando um ponteiro de função pode ser chamada usando ponteiros de função.
O uso de uma função que recebe um retorno de chamada do ponteiro de função é bastante simples:
1.4 Exemplo
Pode ser escrita uma função que não depende de como o retorno de chamada funciona:
onde possíveis retornos de chamada possam ser
usado como
2. Ponteiro para a função membro
Um ponteiro para a função membro (de alguma classe
C
) é um tipo especial de (e ainda mais complexo) ponteiro de função que requer um objeto do tipoC
para operar.2.1 Escrevendo o ponteiro para a função membro / notação de tipo
Um ponteiro para o tipo de função de membro para alguma classe
T
possui a notaçãoonde um ponteiro nomeado para a função membro , em analogia com o ponteiro da função, fica assim:
Exemplo: declarando uma função usando um ponteiro para retorno de chamada da função de membro como um de seus argumentos:
2.2 Notação de chamada de retorno de chamada
A função ponteiro para membro de
C
pode ser chamada, com relação a um objeto do tipoC
, usando operações de acesso de membro no ponteiro desreferenciado. Nota: Parênteses necessários!Nota: Se um ponteiro para
C
estiver disponível, a sintaxe é equivalente (onde o ponteiro para tambémC
deve ser desreferenciado):2.3 Notação de uso de retorno de chamada e tipos compatíveis
Uma função de retorno de chamada usando um ponteiro de função de membro da classe
T
pode ser chamada usando um ponteiro de função de membro da classeT
.O uso de uma função que leva um ponteiro para o retorno de chamada da função de membro também é bastante simples:
3.
std::function
objetos (cabeçalho<functional>
)A
std::function
classe é um invólucro de função polimórfica para armazenar, copiar ou chamar chamadas.3.1 Escrevendo uma
std::function
notação de objeto / tipoO tipo de um
std::function
objeto que armazena uma chamada é semelhante a:3.2 Notação de chamada de retorno
A classe
std::function
foioperator()
definido, o qual pode ser utilizado para invocar o seu alvo.3.3 Retorno de chamada usa notação e tipos compatíveis
O
std::function
retorno de chamada é mais genérico do que ponteiros de função ou ponteiro para função de membro, pois tipos diferentes podem ser passados e implicitamente convertidos em umstd::function
objeto.3.3.1 Ponteiros de função e ponteiros para funções-membro
Um ponteiro de função
ou um ponteiro para a função membro
pode ser usado.
3.3.2 Expressões lambda
Um fechamento sem nome de uma expressão lambda pode ser armazenado em um
std::function
objeto:3.3.3
std::bind
expressõesO resultado de uma
std::bind
expressão pode ser passado. Por exemplo, vinculando parâmetros a uma chamada de ponteiro de função:Onde também os objetos podem ser vinculados como objeto para a chamada do ponteiro para funções-membro:
3.3.4 Objetos funcionais
Objetos de classes com
operator()
sobrecarga adequada também podem ser armazenados dentro de umstd::function
objeto.3.4 Exemplo
Alterando o Exemplo do Ponteiro de Função para Usar
std::function
dá muito mais utilidade a essa função porque (ver 3.3) temos mais possibilidades de usá-la:
4. Tipo de retorno de modelo
Usando modelos, o código que chama o retorno de chamada pode ser ainda mais geral do que usar
std::function
objetos.Observe que os modelos são um recurso em tempo de compilação e uma ferramenta de design para o polimorfismo em tempo de compilação. Se o comportamento dinâmico do tempo de execução for alcançado por meio de retornos de chamada, os modelos ajudarão, mas não induzirão a dinâmica do tempo de execução.
4.1 Escrever (anotações de tipo) e chamar retornos de modelo
Generalizando, ou seja, o
std_ftransform_every_int
código acima pode ser alcançado ainda mais usando modelos:com uma sintaxe ainda mais geral (e fácil) para um tipo de retorno de chamada, sendo um argumento de modelo simples, a ser deduzido:
Nota: A saída incluída imprime o nome do tipo deduzido para o tipo de modelo
F
. A implementação detype_name
é dada no final deste post.A implementação mais geral para a transformação unária de um intervalo faz parte da biblioteca padrão, a saber
std::transform
, que também é modelada com relação aos tipos iterados.4.2 Exemplos usando retornos de chamada modelados e tipos compatíveis
Os tipos compatíveis para o
std::function
método de retorno de chamada modeladostdf_transform_every_int_templ
são idênticos aos tipos mencionados acima (consulte 3.4).No entanto, usando a versão do modelo, a assinatura do retorno de chamada usado pode mudar um pouco:
Nota:
std_ftransform_every_int
(versão sem modelo; veja acima) funciona comfoo
mas não está usandomuh
.O parâmetro de modelo simples de
transform_every_int_templ
pode ser todo tipo de chamada possível.O código acima é impresso:
type_name
implementação usada acimafonte
int b = foobar(a, foo); // call foobar with pointer to foo as callback
, este é um erro de digitação, certo?foo
deve ser um ponteiro para que isso funcione no AFAIK.[conv.func]
do padrão C ++ 11 diz: " Um valor l da função tipo T pode ser convertido em um valor prvalor do tipo" ponteiro para T. " O resultado é um ponteiro para a função. "Esta é uma conversão padrão e, como tal, ocorre implicitamente. Pode-se (é claro) usar o ponteiro de função aqui.Há também a maneira C de fazer retornos de chamada: ponteiros de função
Agora, se você deseja passar métodos de classe como retornos de chamada, as declarações para esses ponteiros de função têm declarações mais complexas, por exemplo:
fonte
typedef
o tipo de retorno de chamada? Isso é possível?typedef
é apenas açúcar sintático para torná-lo mais legível. Semtypedef
a definição de DoWorkObject para ponteiros de função seria:void DoWorkObject(int (*callback)(float))
. Para ponteiros membros seria:void DoWorkObject(int (ClassName::*callback)(float))
Scott Meyers dá um bom exemplo:
Eu acho que o exemplo diz tudo.
std::function<>
é a maneira "moderna" de escrever retornos de chamada em C ++.fonte
Uma função de retorno de chamada é um método que é passado para uma rotina e chamado em algum momento pela rotina para a qual é passado.
Isso é muito útil para criar software reutilizável. Por exemplo, muitas APIs do sistema operacional (como a API do Windows) usam muito os retornos de chamada.
Por exemplo, se você quiser trabalhar com arquivos em uma pasta - você pode chamar uma função API, com sua própria rotina, e sua rotina será executada uma vez por arquivo na pasta especificada. Isso permite que a API seja muito flexível.
fonte
A resposta aceita é muito útil e bastante abrangente. No entanto, o PO afirma
Então, aqui está o C ++ 11,
std::function
portanto, não há necessidade de ponteiros de função e coisas semelhantes:Este exemplo é, de certa forma, real, porque você deseja chamar função
print_hashes
com diferentes implementações de funções hash, para esse propósito forneci uma simples. Ele recebe uma string, retorna um int (um valor de hash da string fornecida) e tudo o que você precisa lembrar da parte da sintaxe éstd::function<int (const std::string&)>
que descreve essa função como um argumento de entrada da função que a chamará.fonte
Não há um conceito explícito de uma função de retorno de chamada em C ++. Os mecanismos de retorno de chamada geralmente são implementados por meio de ponteiros de função, objetos functor ou objetos de retorno de chamada. Os programadores precisam projetar e implementar explicitamente a funcionalidade de retorno de chamada.
Edite com base nos comentários:
Apesar do feedback negativo que esta resposta recebeu, não está errado. Vou tentar fazer um trabalho melhor para explicar de onde venho.
C e C ++ têm tudo o que você precisa para implementar funções de retorno de chamada. A maneira mais comum e trivial de implementar uma função de retorno de chamada é passar um ponteiro de função como argumento de função.
No entanto, funções de retorno de chamada e ponteiros de função não são sinônimos. Um ponteiro de função é um mecanismo de linguagem, enquanto uma função de retorno de chamada é um conceito semântico. Ponteiros de função não são a única maneira de implementar uma função de retorno de chamada - você também pode usar functores e até funções virtuais de variedade de jardins. O que faz uma chamada de função ser um retorno de chamada não é o mecanismo usado para identificar e chamar a função, mas o contexto e a semântica da chamada. Dizer que algo é uma função de retorno de chamada implica uma separação maior que o normal entre a função de chamada e a função específica que está sendo chamada, um acoplamento conceitual mais frouxo entre o chamador e o chamado, com o chamador tendo controle explícito sobre o que é chamado.
Por exemplo, a documentação do .NET para IFormatProvider diz que "GetFormat é um método de retorno de chamada" , mesmo que seja apenas um método de interface comum. Acho que ninguém argumentaria que todas as chamadas de método virtual são funções de retorno de chamada. O que faz de GetFormat um método de retorno de chamada não é a mecânica de como é passado ou invocado, mas a semântica do chamador que escolhe o método GetFormat de qual objeto será chamado.
Alguns idiomas incluem recursos com semântica explícita de retorno de chamada, geralmente relacionados a eventos e manipulação de eventos. Por exemplo, C # tem o tipo de evento com sintaxe e semântica explicitamente projetadas em torno do conceito de retorno de chamada. O Visual Basic possui sua cláusula Handles , que declara explicitamente um método como uma função de retorno de chamada enquanto abstrai o conceito de delegados ou ponteiros de função. Nesses casos, o conceito semântico de um retorno de chamada é integrado ao próprio idioma.
C e C ++, por outro lado, não incorporam o conceito semântico de funções de retorno de chamada quase tão explicitamente. Os mecanismos estão lá, a semântica integrada não. Você pode implementar funções de retorno de chamada muito bem, mas para obter algo mais sofisticado, que inclui a semântica explícita de retorno de chamada, você deve construí-lo sobre o que o C ++ fornece, como o que o Qt fez com seus Sinais e Slots .
Em poucas palavras, o C ++ tem o que você precisa para implementar retornos de chamada, geralmente com bastante facilidade e trivialidade usando ponteiros de função. O que ele não possui são palavras-chave e recursos cuja semântica é específica para retornos de chamada, como raise , emit , Handles , evento + = , etc. Se você vem de um idioma com esses tipos de elementos, o retorno de chamada nativo é suportado em C ++ vai se sentir castrado.
fonte
As funções de retorno de chamada fazem parte do padrão C e, portanto, também fazem parte do C ++. Mas se você estiver trabalhando com C ++, sugiro que você use o padrão observador : http://en.wikipedia.org/wiki/Observer_pattern
fonte
Veja a definição acima, onde afirma que uma função de retorno de chamada é passada para alguma outra função e, em algum momento, é chamada.
Em C ++, é desejável que funções de retorno de chamada chamem um método de classes. Ao fazer isso, você tem acesso aos dados do membro. Se você usar a maneira C de definir um retorno de chamada, precisará apontá-lo para uma função de membro estática. Isso não é muito desejável.
Aqui está como você pode usar retornos de chamada em C ++. Suponha 4 arquivos. Um par de arquivos .CPP / .H para cada classe. Classe C1 é a classe com um método que queremos chamar de retorno. C2 chama de volta ao método de C1. Neste exemplo, a função de retorno de chamada usa 1 parâmetro que eu adicionei para o bem dos leitores. O exemplo não mostra nenhum objeto sendo instanciado e usado. Um caso de uso para esta implementação é quando você tem uma classe que lê e armazena dados no espaço temporário e outra que processa os dados após a postagem. Com uma função de retorno de chamada, para cada linha de dados lida, o retorno de chamada pode processá-lo. Essa técnica elimina a sobrecarga do espaço temporário necessário. É particularmente útil para consultas SQL que retornam uma grande quantidade de dados que precisam ser pós-processados.
fonte
Os sinais do Boost2 permitem que você assine funções-membro genéricas (sem modelos!) E de maneira segura.
fonte
A resposta aceita é abrangente, mas está relacionada à questão, só quero colocar um exemplo simples aqui. Eu tinha um código que escrevi há muito tempo. eu queria atravessar uma árvore de maneira ordenada (nó esquerdo, nó raiz e nó direito) e sempre que chegava a um nó, queria poder chamar uma função arbitrária para que ele pudesse fazer tudo.
fonte