O que são functores C ++ e seus usos?

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?

Konrad
fonte
4
Este assunto foi abordado em resposta a esta pergunta: stackoverflow.com/questions/317450/why-override-operator#317528
Luc Touraille 10/08/08
2
É usado para criar um fechamento em C ++.
copper.hat
Observando as respostas abaixo, se alguém está se perguntando o que operator()(...)significa: está sobrecarregando o operador "chamada de função" . É simplesmente uma sobrecarga do ()operador para o operador. Não se engane operator()em chamar uma função chamada operator, mas veja-a como o operador usual sobrecarregando a sintaxe.
zardosht

Respostas:

1041

Um functor é basicamente apenas uma classe que define o operador (). Isso permite criar objetos com aparência de função:

// this is a functor
struct add_x {
  add_x(int val) : x(val) {}  // Constructor
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument

std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
assert(out[i] == in[i] + 1); // for all i

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::transformdeve chamar. Deveria ligar add_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.

jalf
fonte
32
Você pode explicar esta linha, por favor std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); por que você escreve lá add_x, não o add42?
Alec
102
@ Alecs Ambos teriam funcionado (mas o efeito teria sido diferente). Se eu tivesse usado add42, teria usado o functor que criei anteriormente e adicionei 42 a cada valor. Com add_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.
jalf
8
@zadane é claro. Eles só precisam ter o 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.
jalf
4
@ rikimaru2013 Na linguagem da programação funcional, você está correto, uma função também é um functor, mas na linguagem do C ++, o functor é especificamente uma classe usada como uma função. A terminologia foi um pouco abusada desde o início, mas a divisão é uma distinção útil e, portanto, persiste hoje. Se você começar a se referir a funções como "functors" em um contexto C ++, apenas confundirá a conversa.
srm
6
É uma classe ou uma instância da classe? Na maioria das fontes, add42seria chamado de functor, não add_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?
Sergei Tachenov
121

Pequena adição. Você pode usar boost::function, para criar functors a partir de funções e métodos, desta forma:

class Foo
{
public:
    void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"

e você pode usar boost :: bind para adicionar estado a este functor

boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"

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:

class SomeClass
{
    std::string state_;
public:
    SomeClass(const char* s) : state_(s) {}

    void method( std::string param )
    {
        std::cout << state_ << param << std::endl;
    }
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"

Você pode criar uma lista ou vetor de functores

std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
        events.begin(), events.end(), 
        boost::bind( boost::apply<void>(), _1, e));

Há um problema com tudo isso, as mensagens de erro do compilador não são legíveis por humanos :)

Evgeny Lazin
fonte
4
Não deveria operator ()ser público no seu primeiro exemplo, já que as classes são padronizadas como privadas?
NathanOliver
4
talvez em algum ponto esta resposta merece uma atualização, já que agora lambdas são a maneira mais fácil de obter um functor de qualquer
idclev 463035818
102

Um Functor é um objeto que age como uma função. Basicamente, uma classe que define operator().

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

A vantagem real é que um functor pode manter o estado.

class Matcher
{
   int target;
   public:
     Matcher(int m) : target(m) {}
     bool operator()(int x) { return x == target;}
}

Matcher Is5(5);

if (Is5(n))    // same as if (n == 5)
{ ....}
James Curran
fonte
11
Basta adicionar que eles podem ser usados ​​como um ponteiro de função.
Martin York
7
@LokiAstari - Para aqueles que são novos no conceito, isso pode ser um pouco enganador. Functors podem ser "usados ​​como", mas nem sempre "no lugar de" ponteiros de função. Por exemplo, uma função que usa um ponteiro de função não pode colocar um functor em seu lugar, mesmo que ele tenha os mesmos argumentos e retorne valor que o ponteiro de função. Mas, em geral, ao projetar, os functores são o caminho preferido e, teoricamente, "mais moderno".
MasonWinsauer
Por que o segundo retorna intquando deveria retornar bool? Isso é C ++, não C. Quando essa resposta foi escrita, boolnão existia?
Fund Monica's Lawsuit
@QPaysTaxes Um erro de digitação, eu acho. Provavelmente copiei e copiei o código do primeiro exemplo e esqueci de alterá-lo. Eu consertei agora.
James Curran
1
@Riasat Se o Matcher estiver em uma biblioteca, definir Is5 () é bastante simples. E você pode criar Is7 (), Is32 () etc. Além disso, isso é apenas um exemplo. O functor poderia ser muito mais complicado.
James Curran
51

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:

  • O objeto de função pode ter estado
  • O objeto de função se encaixa no OOP (ele se comporta como qualquer outro objeto).

Contras:

  • Traz mais complexidade ao programa.

Usado em vez do ponteiro de função:

Recursos:

  • O objeto de função geralmente pode ser incorporado

Contras:

  • O objeto de função não pode ser trocado por outro tipo de objeto de função durante o tempo de execução (pelo menos, a menos que estenda alguma classe base, o que, portanto, gera alguma sobrecarga)

Usado em vez da função virtual:

Recursos:

  • O objeto de função (não virtual) não requer o envio de vtable e tempo de execução, portanto, é mais eficiente na maioria dos casos

Contras:

  • O objeto de função não pode ser trocado por outro tipo de objeto de função durante o tempo de execução (pelo menos, a menos que estenda alguma classe base, o que, portanto, gera alguma sobrecarga)
doc
fonte
1
Você pode explicar esses casos de uso em exemplo real? como podemos usar functors como ponteiro de polimorfismo e função?
Milad Khajavi
1
O que realmente significa que um functor mantém estado?
Erogol
obrigado por apontar que é preciso uma classe base para ter algum tipo de polimorfismo. Eu só tenho o problema de precisar usar um functor no mesmo local que um ponteiro de função simples e a única maneira que encontrei foi escrever uma classe base de functor (como não posso usar coisas do C ++ 11). Não tinha certeza se essa sobrecarga faz sentido até eu ler sua resposta.
Idclev 463035818
1
@Erogol Um functor é um objeto que suporta a sintaxe foo(arguments). Portanto, ele pode conter variáveis; por exemplo, se você tivesse uma update_password(string)função, convém acompanhar com que frequência isso aconteceu; com um functor, que pode private long timerepresentar 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.l
Ação do Fundo Monica
4
⁺¹ por mencionar que o nome foi inventado sem motivo. Acabei de procurar qual é a relação entre o functor matemático (ou funcional, se você quiser) e o do C ++.
Hi-Angel
41

Como 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 MultiplyByfunctor que multiplique seu argumento por uma quantidade especificada:

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

Então você pode passar um MultiplyByobjeto para um algoritmo como std :: transform:

int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}

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.

Matthew Crumley
fonte
37

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 + operatorpodem 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 () operatorque é 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 () operatorestá 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)

class myFunctor
{ 
    public:
        /* myFunctor is the constructor. parameterVar is the parameter passed to
           the constructor. : is the initializer list operator. myObject is the
           private member object of the myFunctor class. parameterVar is passed
           to the () operator which takes it and adds it to myObject in the
           overloaded () operator function. */
        myFunctor (int parameterVar) : myObject( parameterVar ) {}

        /* the "operator" word is a keyword which indicates this function is an 
           overloaded operator function. The () following this just tells the
           compiler that () is the operator being overloaded. Following that is
           the parameter for the overloaded operator. This parameter is actually
           the argument "parameterVar" passed by the constructor we just wrote.
           The last part of this statement is the overloaded operators body
           which adds the parameter passed to the member object. */
        int operator() (int myArgument) { return myObject + myArgument; }

    private: 
        int myObject; //Our private member object.
}; 

Se nada disso for impreciso ou simplesmente errado, sinta-se à vontade para me corrigir!

Johanne Irish
fonte
1
O operador () é chamado de operador de chamada de função. Eu acho que você também pode chamá-lo de operador de parênteses.
Gautam
4
"Este parâmetro é na verdade o argumento" parameterVar "passado pelo construtor que acabamos de escrever" Hein?
Lightness Races em órbita
22

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::vectorseguinte:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

Essa função recebe std::vector<T>ae retorna std::vector<U>quando recebe uma função Fque recebe Tae retorna a U. Um functor não precisa ser definido sobre os tipos de contêiner, também pode ser definido para qualquer tipo de modelo, incluindo std::shared_ptr:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

Heres um exemplo simples que converte o tipo em um double:

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

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 que identity(x):

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

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 que fmap(f, fmap(g, x)):

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));
Paul Fultz II
fonte
2
Artigo argumentando que functor deve corretamente ser utilizado para este significado (ver também en.wikipedia.org/wiki/Functor ), e que usá-lo para objetos de função é apenas superficial: jackieokay.com/2017/01/26/functors.html Ele pode ser tarde demais para isso, dado o número de respostas aqui que consideram apenas o significado do objeto de função.
ARMB
2
Essa resposta deve ser aquela com> 700 Upvotes. Como alguém que conhece Haskell melhor que o C ++, a linguagem C ++ me intrigava o tempo todo.
26417 mschmidt
Teoria das categorias e C ++? Esta é a conta secreta de SO de Bartosz Milewski?
Mateen Ulhaq
1
Pode ser útil resumir as leis do functor na notação padrão: fmap(id, x) = id(x)e fmap(f ◦ g, x) = fmap(f, fmap(g, x)).
Mateen Ulhaq
@mschmidt enquanto functor também significa que este, sobrecargas C ++ para o nome significa o mesmo que "objeto de função"
Caleth
9

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.

Companheiro de viagem
fonte
3
Se o seu functor chamou funções específicas diferentes e essas outras funções variaram no número de parâmetros que eles aceitam, isso significa que o seu functor aceitou um número variável de argumentos para enviar para essas outras funções?
johnbakers
4
você pode explicar o cenário acima citando alguma parte do código, eu sou novo para c ++ quiser entender este conceito ..
sanjeev
3

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 .

Yantao Xie
fonte
Este (o exemplo da matriz) é o uso simples, operator()mas não o uso das propriedades do objeto de função.
Renardesque
3

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:

/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
    std::string output;
    Functor(const std::string& out): output(out){}
    operator()() const
    {
        std::cout << output << " ";
    }
};

int main(int argc, char **argv)
{
    std::list<Functor> taskQueue;
    taskQueue.push_back(Functor("this"));
    taskQueue.push_back(Functor("is a"));
    taskQueue.push_back(Functor("very simple"));
    taskQueue.push_back(Functor("and poorly used"));
    taskQueue.push_back(Functor("task queue"));
    for(std::list<Functor>::iterator it = taskQueue.begin();
        it != taskQueue.end(); ++it)
    {
        *it();
    }
    return 0;
}

/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
    std::cout << "i = " << i << std::endl;
    std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
    std::cin >> should_increment;
    return 2;
}
void doSensitiveWork()
{
     ++i;
     should_increment = false;
}
class BaseCoroutine
{
public:
    BaseCoroutine(int stat): status(stat), waiting(false){}
    void operator()(){ status = perform(); }
    int getStatus() const { return status; }
protected:
    int status;
    bool waiting;
    virtual int perform() = 0;
    bool await_status(BaseCoroutine& other, int stat, int change)
    {
        if(!waiting)
        {
            waiting = true;
        }
        if(other.getStatus() == stat)
        {
            status = change;
            waiting = false;
        }
        return !waiting;
    }
}

class MyCoroutine1: public BaseCoroutine
{
public:
    MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
    BaseCoroutine& partner;
    virtual int perform()
    {
        if(getStatus() == 1)
            return doSomeWork();
        if(getStatus() == 2)
        {
            if(await_status(partner, 1))
                return 1;
            else if(i == 100)
                return 0;
            else
                return 2;
        }
    }
};

class MyCoroutine2: public BaseCoroutine
{
public:
    MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
    bool& work_signal;
    virtual int perform()
    {
        if(i == 100)
            return 0;
        if(work_signal)
        {
            doSensitiveWork();
            return 2;
        }
        return 1;
    }
};

int main()
{
     std::list<BaseCoroutine* > coroutineList;
     MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
     MyCoroutine1 *printer = new MyCoroutine1(incrementer);

     while(coroutineList.size())
     {
         for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
             it != coroutineList.end(); ++it)
         {
             *it();
             if(*it.getStatus() == 0)
             {
                 coroutineList.erase(it);
             }
         }
     }
     delete printer;
     delete incrementer;
     return 0;
}

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.

nfries88
fonte
2

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:

void* method(void* something)

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 Threadclasse. Se você queria executar métodos da MyClassclasse, o que eu fiz foi transformar esses métodos em Functorclasses derivadas.

Além disso, a Threadclasse 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 que startThread(..)deve receber em arg é uma void*referência convertida a uma instância no heap de qualquer Functorclasse derivada, que será convertida novamente Functor*quando executada e depois denominada run()método.

erandros
fonte
2

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:

int CTask::ThreeParameterTask(int par1, int par2, int par3)

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.

// a template class for converting a member function of the type int        function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
  public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
    :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
    return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

Além disso, precisamos de um método auxiliar mem_fun3 para a classe acima para ajudar na chamada.

template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm)          (_arg1,_arg2,_arg3) )
{
  return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));

}

Agora, para vincular os parâmetros, precisamos escrever uma função do fichário. Então, aqui vai:

template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
    :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}

 //and this is the function object 
 void operator()() const
 {
        m_fn(m_ptr,m1,m2,m3);//that calls the operator
    }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

E, uma função auxiliar para usar a classe binder3 - bind3:

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

Agora, temos que usar isso com a classe Command; use o seguinte typedef:

typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
    method3 = p_method;
}

Aqui está como você chama:

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
      &CTask::ThreeParameterTask), task1,2122,23 );

Nota: f3 (); chamará o método task1-> ThreeParameterTask (21,22,23) ;.

O contexto completo desse padrão no link a seguir

Alex Punnen
fonte
2

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.

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};
Martin Broadhurst
fonte
1

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.

Yantao Xie
fonte
-10

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 ;-)

JChMathae
fonte
Por que você descreve um functor como um "método sem nome"?
Anderson Green
5
Uma função sem um nome é chamada lambda.
Paul Fultz II 18/08/13