Ao implementar uma função de retorno de chamada em C ++, ainda devo usar o ponteiro de função no estilo C:
void (*callbackFunc)(int);
Ou devo usar std :: function:
std::function< void(int) > callbackFunc;
Ao implementar uma função de retorno de chamada em C ++, ainda devo usar o ponteiro de função no estilo C:
void (*callbackFunc)(int);
Ou devo usar std :: function:
std::function< void(int) > callbackFunc;
Respostas:
Em resumo, use astd::function
menos que você tenha um motivo para não fazê-lo.
Os ponteiros de função têm a desvantagem de não conseguir capturar algum contexto. Você não poderá, por exemplo, transmitir uma função lambda como um retorno de chamada que captura algumas variáveis de contexto (mas funcionará se não capturar nenhuma). Chamar uma variável membro de um objeto (ou seja, não estático) também não é possível, pois o objeto ( this
-pointer) precisa ser capturado. (1)
std::function
(desde C ++ 11) é principalmente para armazenar uma função (distribuí-la não requer que ela seja armazenada). Portanto, se você deseja armazenar o retorno de chamada, por exemplo, em uma variável de membro, provavelmente é sua melhor escolha. Mas também se você não armazená-lo, é uma boa "primeira escolha", embora tenha a desvantagem de introduzir uma sobrecarga (muito pequena) ao ser chamada (portanto, em uma situação muito crítica de desempenho, pode ser um problema, mas na maioria Não deveria). É muito "universal": se você se preocupa muito com códigos consistentes e legíveis e não quer pensar em todas as escolhas que você faz (por exemplo, quer simplificar), use std::function
para todas as funções que passar.
Pense em uma terceira opção: se você está prestes a implementar uma função pequena que relata algo por meio da função de retorno de chamada fornecida, considere um parâmetro de modelo , que pode ser qualquer objeto que possa ser chamado , como ponteiro de função, functor, lambda, a std::function
, ... A desvantagem aqui é que sua função (externa) se torna um modelo e, portanto, precisa ser implementada no cabeçalho. Por outro lado, você obtém a vantagem de que a chamada para o retorno de chamada pode ser incorporada, pois o código do cliente da sua função (externa) "vê" a chamada para o retorno de chamada com as informações exatas do tipo disponíveis.
Exemplo para a versão com o parâmetro template (escreva em &
vez de &&
pré-C ++ 11):
template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
...
callback(...);
...
}
Como você pode ver na tabela a seguir, todos eles têm suas vantagens e desvantagens:
+-------------------+--------------+---------------+----------------+
| | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture | no(1) | yes | yes |
| context variables | | | |
+-------------------+--------------+---------------+----------------+
| no call overhead | yes | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be inlined | no | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be stored | yes | yes | no(2) |
| in class member | | | |
+-------------------+--------------+---------------+----------------+
| can be implemented| yes | yes | no |
| outside of header | | | |
+-------------------+--------------+---------------+----------------+
| supported without | yes | no(3) | yes |
| C++11 standard | | | |
+-------------------+--------------+---------------+----------------+
| nicely readable | no | yes | (yes) |
| (my opinion) | (ugly type) | | |
+-------------------+--------------+---------------+----------------+
(1) Existem soluções alternativas para superar essa limitação, por exemplo, passando os dados adicionais como parâmetros adicionais para sua função (externa): myFunction(..., callback, data)
será chamada callback(data)
. Esse é o "retorno de chamada com argumentos" no estilo C, que é possível em C ++ (e a propósito muito usado na API do WIN32), mas deve ser evitado porque temos melhores opções em C ++.
(2) A menos que estejamos falando sobre um modelo de classe, ou seja, a classe na qual você armazena a função é um modelo. Mas isso significa que, no lado do cliente, o tipo da função decide o tipo do objeto que armazena o retorno de chamada, o que quase nunca é uma opção para casos de uso reais.
(3) Para pré-C ++ 11, use boost::function
std::function
tipo apagado, se precisar armazená-la fora do local imediatamente. chamado contexto).
void (*callbackFunc)(int);
pode ser uma função de retorno de chamada no estilo C, mas é horrivelmente inutilizável e de design ruim.
Um retorno de chamada no estilo C bem projetado parece void (*callbackFunc)(void*, int);
- ele tem void*
que permitir que o código que faz o retorno de chamada mantenha o estado além da função. Não fazer isso força o chamador a armazenar o estado globalmente, o que é indelicado.
std::function< int(int) >
acaba sendo um pouco mais caro que a int(*)(void*, int)
invocação na maioria das implementações. No entanto, é mais difícil para alguns compiladores alinharem. Existem std::function
implementações de clones que rivalizam com a sobrecarga de invocação de ponteiros de função (consulte 'delegados mais rápidos possíveis' etc) que podem aparecer nas bibliotecas.
Agora, os clientes de um sistema de retorno de chamada geralmente precisam configurar recursos e descartá-los quando o retorno de chamada é criado e removido, e estar cientes do tempo de vida útil do retorno de chamada. void(*callback)(void*, int)
não fornece isso.
Às vezes, isso está disponível através da estrutura de código (o retorno de chamada tem vida útil limitada) ou através de outros mecanismos (cancelar o registro de retornos de chamada e similares).
std::function
fornece um meio para gerenciamento de vida útil limitado (a última cópia do objeto desaparece quando é esquecida).
Em geral, eu usaria um a std::function
menos que as preocupações de desempenho se manifestassem. Se o fizessem, primeiro procuraria alterações estruturais (em vez de um retorno de chamada por pixel, que tal gerar um processador de linha de varredura baseado no lambda que você me passou? O que deve ser suficiente para reduzir a sobrecarga de chamada de função para níveis triviais. ) Então, se persistir, escreveria um delegate
delegado com base no mais rápido possível e veria se o problema de desempenho desapareceria.
Eu usaria principalmente ponteiros de função para APIs herdadas ou para criar interfaces C para comunicação entre diferentes códigos gerados por compiladores. Também os usei como detalhes de implementação interna quando estou implementando tabelas de salto, tipo apagamento, etc: quando eu estou produzindo e consumindo, e não o estou expondo externamente para nenhum código de cliente usar, e os ponteiros de função fazem tudo o que preciso .
Observe que você pode escrever wrappers que transformam um retorno de chamada std::function<int(int)>
em int(void*,int)
estilo, supondo que haja uma infraestrutura de gerenciamento de vida útil do retorno de chamada adequada. Portanto, como um teste de fumaça para qualquer sistema de gerenciamento de vida útil de retorno de chamada no estilo C, eu me certificaria de que o acondicionamento std::function
funcionasse razoavelmente bem.
void*
veio? Por que você deseja manter o estado além da função? Uma função deve conter todo o código necessário, todas as funcionalidades, basta passar os argumentos desejados e modificar e retornar algo. Se você precisar de algum estado externo, por que um functionPtr ou um retorno de chamada levará essa bagagem? Eu acho que o retorno de chamada é desnecessariamente complexo.
this
. É porque é necessário considerar o caso de uma função-membro sendo chamada, então precisamos do this
ponteiro para apontar para o endereço do objeto? Se eu estiver errado, você poderia me dar um link para onde posso encontrar mais informações sobre isso, porque não consigo encontrar muito sobre isso. Desde já, obrigado.
void*
para permitir a transmissão do estado de tempo de execução. Um ponteiro de função com a void*
e um void*
argumento pode emular uma chamada de função de membro para um objeto. Desculpe, não conheço um recurso que analise "projetar mecanismos de retorno de chamada C 101".
this
. Foi isso que eu quis dizer. OK, obrigado mesmo assim.
Use std::function
para armazenar objetos de chamada arbitrários. Ele permite que o usuário forneça o contexto necessário para o retorno de chamada; um ponteiro de função simples não.
Se você precisar usar ponteiros de função simples por algum motivo (talvez porque queira uma API compatível com C), adicione um void * user_context
argumento para que seja pelo menos possível (embora inconveniente) acessar o estado que não é passado diretamente para o função.
O único motivo para evitar std::function
é o suporte a compiladores legados que não têm suporte para esse modelo, que foi introduzido no C ++ 11.
Se o suporte à linguagem pré-C ++ 11 não for um requisito, o uso std::function
oferece aos seus chamadores mais opções na implementação do retorno de chamada, tornando-o uma opção melhor em comparação aos ponteiros de função "simples". Ele oferece aos usuários da sua API mais opções, enquanto abstrai as especificidades de sua implementação para o seu código que executa o retorno de chamada.