logo
Contato | Sobre...        
rebarba rebarba

Rodrigo Strauss :: Blog

follow us in feedly

Tutorial de STL, parte 6: Functors

Functors (ou function objects) são objetos que podem ser chamados como funções. No C++, você pode sobrecarregar o operador de chamada de função - operator() - e permitir que um objeto se comporte de forma bem parecida com uma função. A grande vantagem de um objeto em relação a uma função é que o primeiro pode guardar estados diferentes usando instâncias diferentes. Um função pode guardar estado, mas esse estado é único para todos que chamam essa função. Tudo fica mais simples com um exemplo de código:

class FunctorString
{
  std::string m_str;
public:
  FunctorString(const std::string& str) : m_str(str)
  {}
  bool operator ()(const std::string& str)
  {
    return m_str == str;
  }
};

 
int main()
{
  FunctorString comparaComLaranja("laranja"), comparaComBanana("banana");
 
  //
  // todos chamados como função, comparando com
  // coisas diferentes
  //

  std::cout << comparaComLaranja("maçã") << std::endl; // não
  std::cout << comparaComLaranja("banana") << std::endl; // também não
  std::cout << comparaComLaranja("laranja") << std::endl; // agora sim


  std::cout << comparaComBanana("maçã") << std::endl; // não
   std::cout << comparaComBanana("pera") << std::endl; // também não
   std::cout << comparaComBanana("banana") << std::endl; // agora sim

}

Note que o FunctorString pode ser chamado exatamente como uma função, mas criando diferentes instâncias você pode criar comportamentos diferentes. Isso é muito interessante para quando uma função que deve ser rodada sobre uma seqüência STL não é trivial. Exemplo: o algoritmo for_each chama uma função (ou functor) para cada item da sequência. Caso queiramos elevar todos os itens a uma determinada potência x, não podemos usar uma função, pois para cada potência diferente seria necessário criar outra função (esqueça a gambiarra da variável global). Usando um Functor isso fica fácil, é só criar diversas instâncias com a potência diferente.

Functors funcionam bem também para containers com objetos, onde geralmente as comparações ou ações sobre eles não é trivial, quase sempre exigem manipulação de acesso à itens. Além disso, é intessante notar que os algoritmos da STL não aceitam ponteiros de função, mas sim parâmetros cujos tipos são dados de acordo com um template. Ou seja, qualquer coisa que puder ser chamada como uma função - seja um função isolada, função estática de uma classe ou um functor - serve. É o que chamamos de duck typing: se anda como um pato e faz quack como um pato, deve ser um pato.

Segue um código grande, que usa Functors e tem também um exemplo de uso de templates para substituir os Functors por templates no caso do estado do nosso suposto functor ser um tipo:

 
#include "stdafx.h"
#include <assert.h>
#include <vector>
#include <string>

#include <iostream>
 
//
// Código longo mas interessante para demonstrar 
// programação genérica. Pretendo usar essa hierarquia
// para futuras explanações
//
 
struct ProgrammingLanguage
{
  std::string name;
  std::string creator;
  // preciso disso para o dynamic_cast...
  virtual ~ProgrammingLanguage(){} 
};

 
struct CompiledProgrammingLanguage : public ProgrammingLanguage
{
  struct Compiler
  {
    std::string name;
    bool isOpenSource;
  };
 
  std::vector<Compiler> availableCompilers;
};

 
struct NativeCompiledProgrammingLanguage  : public CompiledProgrammingLanguage
{
  bool hasOptimizer;
};
 
enum JitModel
{
  Everything,
  HotFunctions
};
 
struct ByteCodeCompiledProgrammingLanguage : public CompiledProgrammingLanguage
{
  bool hasJit;
  JitModel jitModel;
};

 
struct InterpretedLanguage : public ProgrammingLanguage
{
  bool hasConsole;
};
 
 
void LoadLanguages(std::vector<ProgrammingLanguage*>* pLanguages)
{
  //

  // Alerta de didática:
  // EU SOU CONTRA alocar memória em ponteiro raw, sem
  // gerenciamento. A primeira versão desse código usava
  // boost:shared_ptr, mas tornava os functors mais 
  // complicados por causa de adaptors e coisas assim.
  //
 
  //
  // Python

  //
  InterpretedLanguage* python = new InterpretedLanguage();
  python->name = "Python" ;
  python->creator = "Guido van Rossum";
  python->hasConsole = true;
  pLanguages->push_back(python);
 
  //

  // VBScript
  //
  InterpretedLanguage* vbScript = new InterpretedLanguage();
  vbScript->name = "VBScript" ;
  vbScript->creator = "??";
  vbScript->hasConsole = false;
  pLanguages->push_back(vbScript);
 

  //
  // JavaScript
  //
  InterpretedLanguage* javaScript = new InterpretedLanguage();
  javaScript->name = "JavaScript";
  javaScript->creator = "??";
  javaScript->hasConsole = false;
  pLanguages->push_back(javaScript);

 
  //
  // Java
  //
  ByteCodeCompiledProgrammingLanguage* java = new ByteCodeCompiledProgrammingLanguage();
  java->name = "Java" ;
  java->creator = "James Gosling";
  java->hasJit = true;
  java->jitModel = HotFunctions; // Me corrijam se estou errado

 
  CompiledProgrammingLanguage::Compiler javaCompiler;
  javaCompiler.isOpenSource = true; // Já foi liberado, né?
  javaCompiler.name = "javac";
  java->availableCompilers.push_back(javaCompiler);
 
  pLanguages->push_back(java);
 
  //
  // C#

  //
  ByteCodeCompiledProgrammingLanguage* cSharp = new ByteCodeCompiledProgrammingLanguage();
  cSharp->name = "C#"; 
  cSharp->creator = "Anders Hejlsberg";
  cSharp->hasJit = true;
  cSharp->jitModel = Everything;
 
  CompiledProgrammingLanguage::Compiler microsoftCsCompiler;
  microsoftCsCompiler.isOpenSource = false;
  microsoftCsCompiler.name = "csc";
  cSharp->availableCompilers.push_back(microsoftCsCompiler);

 
  pLanguages->push_back(cSharp);
 
  //
  // C++
  //
  NativeCompiledProgrammingLanguage* cPlusPlus = new NativeCompiledProgrammingLanguage();
  cPlusPlus->name = "C++"; 
  cPlusPlus->creator = "Bjarne Stroustrup";

 
  CompiledProgrammingLanguage::Compiler gcc;
  gcc.isOpenSource = true;
  gcc.name = "GNU C++";
  cPlusPlus->availableCompilers.push_back(gcc);
 
  CompiledProgrammingLanguage::Compiler vc;
  vc.isOpenSource = false;
  vc.name = "Visual C++";
  cPlusPlus->availableCompilers.push_back(vc);
 
  pLanguages->push_back(cPlusPlus);

 
  //
  // Se sua linguagem preferida não está aqui
  // não fique triste, nada pessoal..
  //
}
 
 
//
// Isso não precisa ser um functor, porque
// como vamos filtrar de acordo com o tipo,
// um template já guardar estado do tipo com
// o qual ele foi instânciado

//
template<typename T>
bool IsLanguageType(ProgrammingLanguage* p)
{
  //
  // isso não tem efeito em runtime, mas garante
  // que o tipo que você está usando herda de
  // ProgrammingLanguage
  //

  static_cast<ProgrammingLanguage*>((T*)NULL); 
 
  return dynamic_cast<const T*>(p) != NULL;
}
 
template<typename TDump, typename TIterator>

void DumpLanguagesByType(TIterator first, TIterator last, std::string message)
{
  std::cout << std::endl
    << message << std::endl 
    << std::string(message.size(), '=') << std::endl;
 
  for( ; ; )
  {
    //

    // acha o próximo item com esse 
    // determinado tipo
    //
    first = std::find_if(
      first, 
      last, 
      &IsLanguageType<TDump>);
 
    if(first == last)
      break; // cabô

 
    // só pra garantir...
    assert(dynamic_cast<TDump*>((*first)));
 
    // dump
    std::cout << (*first)->name << std::endl;
 

    ++first;
  }
 
  return;
}
 
//
// Isso é um functor, um objeto que pode ser
// chamado como uma função. A vantagem é que um 
// objeto guarda estado, o que nos permite salvar
// o nome que deve ser procurado nas chamadas 
// seguintes
//
class FindLanguageByName
{
  std::string m_name;
public:
  FindLanguageByName(const std::string& name) : m_name(name)
  {}

 
  bool operator()(ProgrammingLanguage* p)
  {
    return p->name == m_name;
  }
};
 
void FreeLanguage(ProgrammingLanguage* p)
{
  //
  // caso tenhamos mais membros a serem liberados,
  // centralizamos o código de limpeza aqui

  //
  delete p;
}
 
int main()
{
  typedef std::vector<ProgrammingLanguage*> LanguagesVector;
  LanguagesVector languages;
 
  LoadLanguages(&languages);

 
  //
  // encontra as linguagens interpretadas. Nessa caso não usamos
  // functors porque nossa função é um template. Note que como
  // vamos diferenciar pelo tipo, usaremos um tipo como parâmetro.
  // tipo como parâmetro = template
  //
  std::cout << "Linguagens interpretadas" << std::endl 
<< "========================" << std::endl;

 
  for(LanguagesVector::iterator iLanguage = languages.begin() ; ; )
  {
    iLanguage = std::find_if(
      iLanguage, 
      languages.end(), 
      &IsLanguageType<InterpretedLanguage>);
 
    if(iLanguage == languages.end())
      break; // cabô
 
    std::cout << (*iLanguage)->name << std::endl;

 
    ++iLanguage;
  }
 
  //
  // encapusulei o código acima em uma função template.
  // Agora vou fazer isso com o resto dos tipos.
  //
  DumpLanguagesByType<NativeCompiledProgrammingLanguage>(
    languages.begin(), 
    languages.end(),
    "Linguagens compiladas para o native code do processador");

 
  DumpLanguagesByType<ByteCodeCompiledProgrammingLanguage>(
    languages.begin(), 
    languages.end(),
    "Linguagens compiladas para byte code");
 
  //
  // agora vou procurar a linguagem por nome usando um functor.
  // Note que a vantagem é que classe FindLanguageByName guarda
  // a string "Python" para ser usada toda ver que o find_if
  // chamá-la para fazer a comparação

  //
  LanguagesVector::iterator iLanguage = 
    std::find_if(languages.begin(), languages.end(), FindLanguageByName("Python"));
 
  std::cout << std::endl << 
    "Python" << (iLanguage != languages.end() ? 
"" : " não") << " encontrado." << std::endl;

 
  //
  // desalocando tudo
  //
  for_each(languages.begin(), languages.end(), &FreeLanguage);
 
  languages.clear();
 
  return 0;
}

Em 27/03/2007 17:03, por Rodrigo Strauss


  
 
 
Comentários
Daniel | em 30/07/2008 | #
Caro Rodrigo,

Tenho uma dúvida quanto ao código acima: a construção

void LoadLanguages(std::vector<ProgrammingLanguage*>* pLanguages)

poderia ser substituída por

void LoadLanguages(std::vector<ProgrammingLanguage*>::iterator pLanguages)


?????


Obrigado
Rodrigo Strauss | website | em 03/08/2008 | #
No contexto acima, não. O iterator aponta para um item do container, e não para o container em si.
Algo a dizer?
Nome:


Site:


E-mail:


Escreva o número vinte e seis:


 Não mostre meu e-mail no site, não serve pra nada mesmo...

Comentário





Os comentários devem ser sobre assuntos relativos ao post, eu provavelmente apagarei comentários totalmente offtopic. Se quiser me enviar uma mensagem, use o formulário de contato. E não esqueça: isso é um site pessoal e eu me reservo o direito de apagar qualquer comentário ofensivo ou inapropriado.
rebarba rebarba
  ::::