Ponteiros para função
Ponteiros para funções são uma funcionalidade da linguagem C/C++ que permite uma grande flexibilidade na criação de sistema dinâmicos. Neste artigo, irei explicar de forma simples, porém completa, a sintaxe e implementação de ponteiros para funções.
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 você criar um ponteiro para uma função, que irá guardar o endereço de memória que poderá ser executada. Esse tipo de funcionalidade permite técnicas, como por exemplo, um sistema de eventos.
Antes de começar a demonstrar como usar ponteiros para funções, entendam que a sintaxe para trabalhar com eles é estranha, mas depois que se entende a técnica, fica simples entender.
Como C (e C++) é uma linguagem fortemente tipada, nenhum dado pode ser de tipo desconhecido para ser utilizado, e funções não são exceção. 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, é necessário 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 três 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 você pensar na questão de passar ponteiros para função como parametro de outra função, esta facilidade de criar um tipos de dados deixa o código muito mais limpo e facil de entender.
Ponteiros para funções são muito úteis, mas você 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. O formato de 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, é necessário um ponteiro (ou referência) para o objeto do qual queremos chamar o método. 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. Primeiro, 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, chamar 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 próximo post, irei descrever a implementação de uma classe genérica (usanto templates) que facilita a manipulação de ponteiros para métodos.
Leave a Reply