Eu continuo ouvindo muito sobre functores em C ++. Alguém pode me dar uma visão geral sobre o que são e em que casos seriam úteis?
876
Eu continuo ouvindo muito sobre functores em C ++. Alguém pode me dar uma visão geral sobre o que são e em que casos seriam úteis?
operator()(...)
significa: está sobrecarregando o operador "chamada de função" . É simplesmente uma sobrecarga do()
operador para o operador. Não se enganeoperator()
em chamar uma função chamadaoperator
, mas veja-a como o operador usual sobrecarregando a sintaxe.Respostas:
Um functor é basicamente apenas uma classe que define o operador (). Isso permite criar objetos com aparência de função:
Há algumas coisas boas sobre os functores. Uma é que, diferentemente das funções regulares, elas podem conter estado. O exemplo acima cria uma função que adiciona 42 ao que você der. Mas esse valor 42 não é codificado, foi especificado como um argumento construtor quando criamos nossa instância de functor. Eu poderia criar outro adicionador, que adicionou 27, apenas chamando o construtor com um valor diferente. Isso os torna bem personalizáveis.
Como as últimas linhas mostram, você geralmente passa functors como argumentos para outras funções, como std :: transform ou outros algoritmos de biblioteca padrão. Você poderia fazer o mesmo com um ponteiro de função regular, exceto, como eu disse acima, que os functores podem ser "personalizados" porque contêm estado, tornando-os mais flexíveis (se eu quisesse usar um ponteiro de função, teria que escrever uma função que adicionou exatamente 1. ao seu argumento.O functor é geral e adiciona tudo o que você inicializou) e também é potencialmente mais eficiente. No exemplo acima, o compilador sabe exatamente qual função
std::transform
deve chamar. Deveria ligaradd_x::operator()
. Isso significa que ele pode incorporar essa chamada de função. E isso a torna tão eficiente como se eu tivesse chamado manualmente a função em cada valor do vetor.Se eu tivesse passado um ponteiro de função, o compilador não poderia ver imediatamente para qual função ele aponta, portanto, a menos que execute algumas otimizações globais bastante complexas, teria que desreferenciar o ponteiro em tempo de execução e fazer a chamada.
fonte
add42
, teria usado o functor que criei anteriormente e adicionei 42 a cada valor. Comadd_x(1)
eu crio uma nova instância do functor, que adiciona apenas 1 a cada valor. É simplesmente para mostrar que, muitas vezes, você instancia o functor "on the fly", quando necessário, em vez de criá-lo primeiro e mantê-lo por perto antes de usá-lo para qualquer coisa.operator()
, porque é isso que o chamador usa para invocá-lo. O que mais o functor tem de funções-membro, construtores, operadores e variáveis-membro é totalmente sua.add42
seria chamado de functor, nãoadd_x
(que é a classe do functor ou apenas a classe functor). Acho essa terminologia consistente porque os functores também são chamados de objetos de função , não classes de função. Você pode esclarecer esse ponto?Pequena adição. Você pode usar
boost::function
, para criar functors a partir de funções e métodos, desta forma:e você pode usar boost :: bind para adicionar estado a este functor
e mais útil, com a função boost :: bind e boost ::, você pode criar functor a partir do método class, na verdade este é um delegado:
Você pode criar uma lista ou vetor de functores
Há um problema com tudo isso, as mensagens de erro do compilador não são legíveis por humanos :)
fonte
operator ()
ser público no seu primeiro exemplo, já que as classes são padronizadas como privadas?Um Functor é um objeto que age como uma função. Basicamente, uma classe que define
operator()
.A vantagem real é que um functor pode manter o estado.
fonte
int
quando deveria retornarbool
? Isso é C ++, não C. Quando essa resposta foi escrita,bool
não existia?O nome "functor" tem sido tradicionalmente usado na teoria das categorias muito antes de o C ++ aparecer em cena. Isso não tem nada a ver com o conceito de functor em C ++. É melhor usar o nome da função de objeto em vez do que chamamos de "functor" em C ++. É assim que outras linguagens de programação chamam construções semelhantes.
Usado em vez da função comum:
Recursos:
Contras:
Usado em vez do ponteiro de função:
Recursos:
Contras:
Usado em vez da função virtual:
Recursos:
Contras:
fonte
foo(arguments)
. Portanto, ele pode conter variáveis; por exemplo, se você tivesse umaupdate_password(string)
função, convém acompanhar com que frequência isso aconteceu; com um functor, que podeprivate long time
representar o carimbo de data e hora da última vez que aconteceu. Com um ponteiro de função ou função simples, você precisa usar um fora variável de seu namespace, que só está directamente relacionada por documentação e uso, em vez de definition.lComo outros já mencionaram, um functor é um objeto que age como uma função, ou seja, sobrecarrega o operador de chamada de função.
Functors são comumente usados em algoritmos STL. Eles são úteis porque podem manter o estado antes e entre chamadas de função, como um fechamento em linguagens funcionais. Por exemplo, você pode definir um
MultiplyBy
functor que multiplique seu argumento por uma quantidade especificada:Então você pode passar um
MultiplyBy
objeto para um algoritmo como std :: transform:Outra vantagem de um functor sobre um ponteiro para uma função é que a chamada pode ser incorporada em mais casos. Se você passou um ponteiro de função para
transform
, a menos que essa chamada tenha sido incorporada e o compilador saiba que você sempre passa a mesma função para ela, ela não poderá incorporar a chamada através do ponteiro.fonte
Para os novatos como eu, entre nós: depois de um pouco de pesquisa, descobri o que o código publicado pela metade fazia.
Um functor é um objeto de classe ou estrutura que pode ser "chamado" como uma função. Isso é possível sobrecarregando o
() operator
. O() operator
(não sabe como é chamado) pode receber qualquer número de argumentos. Outros operadores usam apenas dois, ou seja, eles+ operator
podem usar apenas dois valores (um de cada lado do operador) e retornar qualquer valor que você tenha sobrecarregado. Você pode ajustar qualquer número de argumentos dentro de um() operator
que é o que lhe confere flexibilidade.Para criar um functor primeiro, você cria sua classe. Em seguida, você cria um construtor para a classe com um parâmetro de sua escolha, tipo e nome. Isso é seguido na mesma instrução por uma lista de inicializadores (que usa um operador de dois pontos, algo para o qual eu também era novo) que constrói os objetos de membro da classe com o parâmetro declarado anteriormente para o construtor. Então o
() operator
está sobrecarregado. Finalmente, você declara os objetos particulares da classe ou estrutura que você criou.Meu código (achei os nomes das variáveis de jalf confusos)
Se nada disso for impreciso ou simplesmente errado, sinta-se à vontade para me corrigir!
fonte
Um functor é uma função de ordem superior que aplica uma função aos tipos parametrizados (isto é, modelo). É uma generalização da função de ordem superior do mapa . Por exemplo, poderíamos definir um functor para o
std::vector
seguinte:Essa função recebe
std::vector<T>
ae retornastd::vector<U>
quando recebe uma funçãoF
que recebeT
ae retorna aU
. Um functor não precisa ser definido sobre os tipos de contêiner, também pode ser definido para qualquer tipo de modelo, incluindostd::shared_ptr
:Heres um exemplo simples que converte o tipo em um
double
:Existem duas leis que os functores devem seguir. A primeira é a lei de identidade, que afirma que, se o functor recebe uma função de identidade, deve ser o mesmo que aplicar a função de identidade ao tipo, ou seja,
fmap(identity, x)
deve ser o mesmo queidentity(x)
:A próxima lei é a lei de composição, que afirma que, se o functor receber uma composição de duas funções, deve ser o mesmo que aplicar o functor para a primeira função e, em seguida, novamente para a segunda função. Portanto,
fmap(std::bind(f, std::bind(g, _1)), x)
deve ser o mesmo quefmap(f, fmap(g, x))
:fonte
fmap(id, x) = id(x)
efmap(f ◦ g, x) = fmap(f, fmap(g, x))
.Aqui está uma situação real em que fui forçado a usar um Functor para resolver meu problema:
Eu tenho um conjunto de funções (digamos, 20 delas), e todas são idênticas, exceto que cada uma chama uma função específica diferente em 3 pontos específicos.
Isso é desperdício incrível e duplicação de código. Normalmente, eu apenas passava um ponteiro de função e chamava isso nos 3 pontos. (Portanto, o código precisa aparecer apenas uma vez, em vez de vinte vezes.)
Mas então percebi, em cada caso, a função específica exigia um perfil de parâmetro completamente diferente! Às vezes 2 parâmetros, às vezes 5 parâmetros, etc.
Outra solução seria ter uma classe base, onde a função específica é um método substituído em uma classe derivada. Mas eu realmente quero construir toda essa herança, só para que eu possa passar um ponteiro de função ????
SOLUÇÃO: Então, o que fiz foi criar uma classe de wrapper (um "Functor") capaz de chamar qualquer uma das funções necessárias. Eu o configurei antecipadamente (com seus parâmetros, etc) e depois o passo em vez de um ponteiro de função. Agora, o código chamado pode acionar o Functor, sem saber o que está acontecendo por dentro. Pode até chamá-lo várias vezes (eu precisava chamá-lo três vezes).
É isso aí - um exemplo prático em que um Functor se mostrou a solução óbvia e fácil, o que me permitiu reduzir a duplicação de código de 20 funções para 1.
fonte
Exceto pelos usados no retorno de chamada, os functores C ++ também podem ajudar a fornecer um estilo de acesso semelhante ao Matlab a uma classe de matriz . Há um exemplo .
fonte
operator()
mas não o uso das propriedades do objeto de função.Como foi repetido, functores são classes que podem ser tratadas como funções (operador de sobrecarga ()).
Eles são mais úteis para situações nas quais você precisa associar alguns dados a chamadas repetidas ou atrasadas para uma função.
Por exemplo, uma lista vinculada de functores pode ser usada para implementar um sistema síncrono básico de corotina, um despachante de tarefas ou análise de arquivos interruptíveis. Exemplos:
Obviamente, esses exemplos não são úteis em si mesmos. Eles apenas mostram como os functores podem ser úteis, eles próprios são muito básicos e inflexíveis e isso os torna menos úteis do que, por exemplo, o que o impulso proporciona.
fonte
Functors são usados no gtkmm para conectar algum botão da GUI a uma função ou método C ++ real.
Se você usar a biblioteca pthread para tornar seu aplicativo multithread, os Functors podem ajudá-lo.
Para iniciar um encadeamento, um dos argumentos de
pthread_create(..)
é o ponteiro da função a ser executado em seu próprio encadeamento.Mas há um inconveniente. Esse ponteiro não pode ser um ponteiro para um método, a menos que seja um método estático ou a menos que você especifique sua classe , como
class::method
. E outra coisa, a interface do seu método pode ser apenas:Portanto, você não pode executar (de uma maneira simples e óbvia) métodos da sua classe em um thread sem fazer algo extra.
Uma maneira muito boa de lidar com threads em C ++ é criar sua própria
Thread
classe. Se você queria executar métodos daMyClass
classe, o que eu fiz foi transformar esses métodos emFunctor
classes derivadas.Além disso, a
Thread
classe possui este método:static void* startThread(void* arg)
Um ponteiro para esse método será usado como argumento a ser chamado
pthread_create(..)
. E o questartThread(..)
deve receber em arg é umavoid*
referência convertida a uma instância no heap de qualquerFunctor
classe derivada, que será convertida novamenteFunctor*
quando executada e depois denominadarun()
método.fonte
Para adicionar, usei objetos de função para ajustar um método legado existente ao padrão de comando; (único lugar em que a beleza do paradigma OO era verdadeira OCP); Também adicionando aqui o padrão do adaptador de função relacionado.
Suponha que seu método tenha a assinatura:
Veremos como podemos ajustá-lo para o padrão Command - para isso, primeiro, você precisa escrever um adaptador de função membro para que ele possa ser chamado como objeto de função.
Nota - isso é feio, e pode ser que você possa usar os auxiliares de ligação do Boost, etc., mas se você não puder ou não quiser, esse é um caminho.
Além disso, precisamos de um método auxiliar mem_fun3 para a classe acima para ajudar na chamada.
}
Agora, para vincular os parâmetros, precisamos escrever uma função do fichário. Então, aqui vai:
E, uma função auxiliar para usar a classe binder3 - bind3:
Agora, temos que usar isso com a classe Command; use o seguinte typedef:
Aqui está como você chama:
Nota: f3 (); chamará o método task1-> ThreeParameterTask (21,22,23) ;.
O contexto completo desse padrão no link a seguir
fonte
Uma grande vantagem da implementação de funções como functors é que eles podem manter e reutilizar o estado entre as chamadas. Por exemplo, muitos algoritmos de programação dinâmica, como o algoritmo Wagner-Fischer para calcular a distância de Levenshtein entre cadeias, funcionam preenchendo uma grande tabela de resultados. É muito ineficiente alocar essa tabela toda vez que a função é chamada, portanto, implementar a função como um functor e tornar a tabela uma variável membro pode melhorar muito o desempenho.
Abaixo está um exemplo de implementação do algoritmo Wagner-Fischer como um functor. Observe como a tabela é alocada no construtor e depois reutilizada
operator()
, com o redimensionamento conforme necessário.fonte
O functor também pode ser usado para simular a definição de uma função local dentro de uma função. Consulte a pergunta e outra .
Mas um functor local não pode acessar variáveis automáticas externas. A função lambda (C ++ 11) é uma solução melhor.
fonte
Eu "descobri" um uso muito interessante de functors: eu os uso quando não tenho um bom nome para um método, pois um functor é um método sem nome ;-)
fonte