quarta-feira, 14 de março de 2012

Funtores em C++, parte I

Ponteiros para funções são legais. Mas são muito C. Para deixar nosso código mais C++, podemos usar Funtores. Funtores são classes que possuem o operator (); dessa maneira podemos chamar a função usando o identificador do objeto como uma função. Vamos transformar a função do artigo anterior em um funtor.
class Function{
 public:
  double operator ()(double x){
   return x * x - 1.0;
  }
 };
Para utilizar o funtor temos que criar uma instância dessa classe, e chamá-la através da referência ao objeto.
 Function f;
 cout << f(2);   
Para usar nossa função root, então, precisaríamos passar uma referência ao objeto:
 double root(Function &f, double i, double e);
O problema aqui é que perdemos a generalidade da função root, já que ela só funciona para um objeto do tipo Function. Para torná-la mais genérica, vamos utilizar polimorfismo. Abaixo está o código completo da solução. Na linha 6 definimos uma classe abstrata para funções, depois criamos a classe MyFunction que vai herdar de Function, sobreescremos então o operator(). Na linha 18, temos a nova assinatura da função, agora ela recebe um ponteiro para um objeto do tipo Function. Assim, com o operador new podemos criar qualquer tipo de classe que derive de Function como parâmetro de nossa função root.
 template <class T>
 bool eqlZero(T x) {
   return x * x < 0.000000001;
 }

 class Function{
 public:
  virtual double operator ()(double x) = 0;
 };
 
 class MyFunction : public Function{
 public:
  double operator ()(double x){
   return x * x - 1.0;
  }
 };
 // finds one root between i and e
 double root(Function* f, double i, double e) {
   int iterationsLeft = 10000;
   while(iterationsLeft--) {
     double x = (i+e) / 2.0;
     cout << x << endl;
     if(eqlZero((*f)(x))){
       return x;
     }else if((*f)(x) < 0) {
       i = x;
     }else {
       e = x;
     }
   }
 }

 int _tmain(int argc, _TCHAR* argv[]) {
   Function* f = new MyFunction();
   double result = root(f, -10.0, 10.0);
   cout << result;

   return 0;
 }
Teremos assim o mesmo resultado que a função do artigo anterior. Mas além de estilística, não existe uma razão aqui para usar funtores (muito pelo contrário, vamos ter uma penalidade de desempenho devido ao overhead). Na próxima parte vamos ver a vantagem de usar funtores.

domingo, 11 de março de 2012

Ponteiros para funções em c++

Ponteiros são legais, mais legal ainda são ponteiros para funções. Imagina que queremos criar uma função que encontra uma raiz de uma função em um intervalo, teremos, então, uma função que tem uma função como parâmetro.

Sua assinatura vai ser assim:
  double root(double (*f)(double), double i, double e);
O "f" é o identificador do ponteiro, do lado esquerdo fica o valor de retorno da função, e entre parênteses, do lado direito, os parâmetros. Imagina que quiséssemos um ponteiro para uma função com dois parâmetros, teríamos então:
double (*f)(double, double);
Então se quiséssemos a usar no corpo da função usaríamos, por exemplo:
 f(1.0, 5.2);
Aqui está o meu código, que acha a raíz da função x² - 1. Se quisermos usar a função root para outra função, só precisamos trocar o ponteiro.
    template <class T>
    bool eqlZero(T x) {
        return x * x < 0.000000001;
    }

    double f(double x){
        return x * x - 1.0;
    }

    // finds one root between i and e
    double root(double (*f)(double), double i, double e) {
        int iterationsLeft = 10000;
        while(iterationsLeft--) {
            double x = (i+e) / 2.0;
            cout << x << endl;
            if(eqlZero(f(x))){
                return x;
            } else if(f(x) < 0) {
                i = x;
            } else {
                e = x;
            }
        }
    }

    int _tmain(int argc, _TCHAR* argv[])
    {
        double result = root(f, -10.0, 10.0);
        cout << result;

        return 0;
    }
Esse código dá como saída o seguinte (que é quando vale a variável x a cada iteração):
0
5
2.5
1.25
0.625
0.9375
1.09375
1.01563
0.976563
0.996094
1.00586
1.00098
0.998535
0.999756
1.00037
1.00006
0.999908
0.999985
0.999985
Outra forma de implementar essa função root genérica, seria com funtores (classes com o operator ()), mas isso seria demais para esse caso. Saiba mais sobre ponteiros para funções.