Ponteiros de função
Uma função nada mais é, em sua essência, do que apenas um conjunto de instruções binárias em alguma posição de memória. Sendo assim, então, o C/C++ permite voce criar um ponteiro para uma função, que irá guardar o endereço de memória da função que poderá ser executada. Esse tipo de funcionalidade permite técnicas como de bom exemplo sistema de eventos.
Antes de começar a demonstrar como usar ponteiro para funções, vou deixar avisado que a sintaxe para os definir é um tanto quanto esdruxula, então nao se assustem muito.
Como C (e C++) é uma linguagem fortemente tipada, nenhum dado pode ser de tipo desconhecido para ser utilizado, e funções nao sao exceçao. Alem disso, funções tem o agravante de não apenas terem um tipo de dado de retorno, mas também a possibilidade de receber n parametros. Então, ao declarar um ponteiro para função, necessitamos declarar seu tipo de retorno e os tipos de todos os parametros. Voce consegue o endereço de uma função usando o nome da função, sem o operador (). O uso do operador & antes do nome da função é, neste caso, opcional. Porém, mais além iremos ver um caso onde o seu uso é obrigatório. Vamos ver alguns exemplos:
// exemplo de função 01
void vazia() {}
// seu ponteiro e atribuiçao
void (*fpVazia)(void) = vazia;
// exemplo de função 02
int numero() { return 2; }
// seu ponteiro e atribuição
int (*fpNumero)(void) = numero;
// exemplo de função 03
int soma(int a, int b) { return a+b; }
// seu ponteiro e atribuição
int (*fpSoma)(int,int) = soma;
Como disse anteriormente, a sintaxe é ligeiramente estranha, mas funciona. Temos aqui tres ponteiros para funções. Agora para executar cada uma dessas, usamos a seguinte sintaxe:
// executa cada uma das funções anteriores fpVazia(); int num = fpNumero(); // num recebe 2 int res = fpSoma(2,2); // res recebe 4 // outras formas de executar: (fpVazia)(); (*fpVazia)();
Repare que eu coloquei algumas outras formas de executar no final do código. Embora todas elas funcionem na maioria dos compiladores, alguns compiladores mais antigos podem dar problema com um ou outro formato. Além disso, para executar ponteiros para métodos (funções membros de classe), a sintaxe para chamadas de função vai ficar menos flexivel.
Para facilitar um pouco a sintaxe de uso de ponteiros para função, podemos utilizar o operador typedef, como exemplifico a seguir:
// exemplo de declaração sem typedef void (*funcExemplo)(int,int) fpExemplo; // agora usando typedef para criar // um tipo de dado novo que é um // ponteiro para função typedef void (*funcExemplo)(int,int); // declarando o ponteiro funcExemplo fpExemplo;
Muito mais simples, não? se vc pensar na questão de passar ponteiros para função como parametro de outra função, esta facilidade de criar um tipo de dado deixa o código muito mais limpo e facil de entender.
Ponteiros para funções são muito úteis, mas voce deve estar pensando “posso usar isso para criar ponteiros de métodos de classes?”. A resposta carrega junto uma explicação:
Um método estático de uma classe é exatamente igual a uma função comum – apenas que o compilador mantém controle sobre as permissões de acesso. Um método não-estático, porém, tem uma diferença bem especial. O formato de chamada ( chamada _thiscall), envia internamente um parametro extra – um ponteiro para o objeto no qual o método está sendo chamado. Disso podemos tirar que, além de sabermos o endereço dessa função, necessitados do ponteiro (ou referência) para o objeto também. Aqui está o exemplo de um ponteiro para um método:
// vamos ver uma classe
class A
{
private:
int _a;
public:
A(int a) : _a(a) {}
~A() {}
// método a ser executado
int soma(int b) { return _a + b; }
};
// criação do tipo ponteiro 'funcSoma'
typedef int (A::*funcSoma)(int);
// criando ponteiro
funcSoma fpSoma = &A::soma;
// nao podemos chamar a função sem um
// objeto do tipo A
A a(2);
A p = new A(3);
// chamando função pelo ponteiro
int res;
res = (a.*fpSoma)(2); // res recebe 4
res = (p->*fpSoma)(2); // res recebe 5
Repare em algumas peculariedades. Primeiramente, inicializamos o ponteiro passando o caminho do método ( &A::soma – diz q soma pertence à classe A. Neste caso, o uso do operador & passa a ser obrigatório). Como disse anteriormente, chama o método sem uma referência a um objeto da classe é impossível, então criamos os objetos ‘a’ e ‘p’ do tipo A. Finalmente, vemos os métodos sendo executados. Repare bem na sintaxe: voce usa o próprio objeto para chamar o método. No caso, dependendo do tipo (se é uma referência ou um ponteiro), usamos um operador diferente. ‘ .* ‘ ou ‘ ->* ‘.
Num outro post, irei descrever a implementação de uma classe genérica (templated) que facilita a manipulação de ponteiro para métodos.
3 comentários até agora
Leave a reply
Fantástico, parabéns pelo artigo.
Você pode colocar um exemplo prático desse artigo?
[...] Me pediram para postar um exemplo de um sistema de eventos usando o assunto do post passado (ponteiros para funções e métodos), mas decidi que antes de fazer esse post, irei falar sobre functors – pois irei utilizar esse [...]
Olá Daniel, parabéns pelo artigo!
Venho dizer que na PDJzine #3 eu escrevi um artigo sobre implementação e uso de FSM’s usando ponteiro de funções. Dá uma olhada lá! (Dica para quem quiser ver outro exemplo onde isso pode ser aplicado)
Ah, e posso adicionar seu blog no meu?
Abraços!