Qual é o ponto dos ponteiros de função?

94

Tenho problemas para ver a utilidade dos ponteiros de função. Acho que pode ser útil em alguns casos (eles existem, afinal), mas não consigo pensar em um caso em que seja melhor ou inevitável usar um ponteiro de função.

Você poderia dar algum exemplo de bom uso de ponteiros de função (em C ou C ++)?

grama
fonte
1
Você pode encontrar um pouco de discussão sobre ponteiros de função nesta questão SO relacionada .
itsmatt de
20
@itsmatt: Na verdade, não. "Como funciona uma TV?" é uma pergunta bem diferente de "O que eu faço com uma TV?"
sbi
6
Em C ++, você provavelmente usaria um functor ( en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B ).
kennytm,
11
Nos velhos tempos sombrios, quando C ++ era "compilado" para C, você podia realmente ver como os métodos virtuais são implementados - sim, com ponteiros de função.
sbk
1
Muito essencial quando você deseja usar C ++ com um C ++ gerenciado ou C #, ou seja: delegados e retornos de chamada
Maher

Respostas:

108

A maioria dos exemplos resumem-se a retornos de chamada : você chama uma função f()passando o endereço de outra função g()e f()chama g()alguma tarefa específica. Se você passar f()o endereço de h(), em vez disso, f()ligará de volta h().

Basicamente, essa é uma maneira de parametrizar uma função: alguma parte de seu comportamento não está f()embutida no código , mas na função de retorno de chamada. Os chamadores podem fazer com que f()se comportem de maneira diferente, passando diferentes funções de retorno de chamada. Um clássico é qsort()da biblioteca padrão C, que usa seu critério de classificação como um ponteiro para uma função de comparação.

Em C ++, isso geralmente é feito usando objetos de função (também chamados de functores). Esses são objetos que sobrecarregam o operador de chamada de função, portanto, você pode chamá-los como se fossem uma função. Exemplo:

class functor {
  public:
     void operator()(int i) {std::cout << "the answer is: " << i << '\n';}
};

functor f;
f(42);

A ideia por trás disso é que, ao contrário de um ponteiro de função, um objeto de função pode transportar não apenas um algoritmo, mas também dados:

class functor {
  public:
     functor(const std::string& prompt) : prompt_(prompt) {}
     void operator()(int i) {std::cout << prompt_ << i << '\n';}
  private:
     std::string prompt_;
};

functor f("the answer is: ");
f(42);

Outra vantagem é que às vezes é mais fácil fazer chamadas embutidas para objetos de função do que chamadas por meio de ponteiros de função. Esta é a razão pela qual classificar em C ++ às vezes é mais rápido do que classificar em C.

sbi
fonte
1
+1, veja também esta resposta para outro exemplo: stackoverflow.com/questions/1727824/…
sharptooth
Você esqueceu as funções virtuais; essencialmente, elas também são ponteiros de função (juntamente com uma estrutura de dados que o compilador gera). Além disso, em C puro, você mesmo pode criar essas estruturas para escrever código orientado a objetos, conforme visto na camada VFS (e em muitos outros lugares) do kernel do Linux.
Florian
2
@krynr: Funções virtuais são ponteiros de função apenas para implementadores de compiladores, e se você tiver que perguntar para que eles servem, provavelmente (espero!) provavelmente não precisará implementar um mecanismo de função virtual de compilador.
sbi
@sbi: Você está certo, é claro. No entanto, acho que ajuda a entender o que está acontecendo dentro da abstração. Além disso, implementar sua própria vtable em C e escrever código orientado a objetos proporciona uma experiência de aprendizado muito boa.
Florian
pontos brownie por fornecer a resposta para a vida, o universo e tudo junto com o que foi pedido por OP
simplename
41

Bem, eu geralmente os uso (profissionalmente) em tabelas de salto (veja também esta questão StackOverflow ).

As tabelas de salto são comumente (mas não exclusivamente) usadas em máquinas de estado finito para torná-las controladas por dados. Em vez de switch / case aninhado

  switch (state)
     case A:
       switch (event):
         case e1: ....
         case e2: ....
     case B:
       switch (event):
         case e3: ....
         case e1: ....

você pode fazer uma matriz 2d de ponteiros de função e apenas chamar handleEvent[state][event]

Mawg diz reintegrar Monica
fonte
24

Exemplos:

  1. Classificação / pesquisas personalizadas
  2. Padrões diferentes (como Estratégia, Observador)
  3. Callbacks
Andrey
fonte
1
A tabela de salto é um importante uso dela.
Ashish,
Se houvesse alguns exemplos funcionados, isso teria meu voto positivo.
Donal Fellows
1
Estratégia e observador são provavelmente melhor implementados usando funções virtuais, se C ++ estiver disponível. Caso contrário, +1.
Billy ONeal,
Acho que o uso inteligente de ponteiros de função pode tornar o observador mais compacto e leve
Andrey,
@BillyONeal Somente se você seguir rigidamente a definição do GoF, com seus Javaisms vazando. Eu descreveria std::sorto compparâmetro de como uma estratégia
Caleth
10

O exemplo "clássico" para a utilidade dos ponteiros de função é a qsort()função de biblioteca C , que implementa uma Classificação Rápida. Para ser universal para toda e qualquer estrutura de dados que o usuário possa criar, são necessários alguns ponteiros vazios para dados classificáveis ​​e um ponteiro para uma função que sabe como comparar dois elementos dessas estruturas de dados. Isso nos permite criar nossa função de escolha para o trabalho e, de fato, até permite escolher a função de comparação em tempo de execução, por exemplo, para classificar em ordem crescente ou decrescente.

Carl Smotricz
fonte
7

Concorde com todos os itens acima, mais .... Quando você carrega uma dll dinamicamente em tempo de execução, você precisa de ponteiros de função para chamar as funções.

Rico
fonte
1
Eu faço isso o tempo todo para oferecer suporte ao Windows XP e ainda usar os recursos do Windows 7. +1.
Billy ONeal,
7

Eu vou contra a corrente aqui.

Em C, os ponteiros de função são a única maneira de implementar a personalização, porque não há OO.

Em C ++, você pode usar ponteiros de função ou functores (objetos de função) para o mesmo resultado.

Os functores têm uma série de vantagens sobre os ponteiros de função bruta, devido à sua natureza de objeto, notavelmente:

  • Eles podem apresentar várias sobrecargas do operator()
  • Eles podem ter estado / referência a variáveis ​​existentes
  • Eles podem ser construídos no local ( lambdae bind)

Eu pessoalmente prefiro functores do que ponteiros de função (apesar do código clichê), principalmente porque a sintaxe para ponteiros de função pode facilmente ficar complicada (do Tutorial de ponteiro de função ):

typedef float(*pt2Func)(float, float);
  // defines a symbol pt2Func, pointer to a (float, float) -> float function

typedef int (TMyClass::*pt2Member)(float, char, char);
  // defines a symbol pt2Member, pointer to a (float, char, char) -> int function
  // belonging to the class TMyClass

A única vez que vi ponteiros de função usados ​​onde functores não podiam foi em Boost.Spirit. Eles abusaram totalmente da sintaxe para passar um número arbitrário de parâmetros como um único parâmetro de modelo.

 typedef SpecialClass<float(float,float)> class_type;

Mas como os modelos variados e lambdas estão chegando, não tenho certeza se usaremos ponteiros de função em código C ++ puro por muito tempo.

Matthieu M.
fonte
Só porque você não vê seus ponteiros de função, não significa que você não os use. Toda vez (a menos que o compilador possa otimizá-la) você chama uma função virtual, usa boost bindou functionusa ponteiros de função. É como dizer que não usamos ponteiros em C ++ porque usamos ponteiros inteligentes. De qualquer forma, estou minando.
Florian
3
@krynr: Discordo educadamente. O que importa é o que você e digita , essa é a sintaxe que você usa. Não deve importar para você como tudo funciona nos bastidores: é disso que se trata a abstração .
Matthieu M.
5

Em C, o uso clássico é a função qsort , onde o quarto parâmetro é um ponteiro para uma função a ser usada para realizar a ordenação dentro da classificação. Em C ++, haveria tendência de usar functores (objetos que se parecem com funções) para esse tipo de coisa.


fonte
2
@KennyTM: Eu estava apontando a única outra instância disso na biblioteca C padrão. Os exemplos que você cita fazem parte de bibliotecas de terceiros.
Billy ONeal,
5

Usei ponteiros de função recentemente para criar uma camada de abstração.

Tenho um programa escrito em C puro que funciona em sistemas embarcados. Ele suporta várias variantes de hardware. Dependendo do hardware que estou executando, ele precisa chamar diferentes versões de algumas funções.

No momento da inicialização, o programa descobre em qual hardware está sendo executado e preenche os ponteiros de função. Todas as rotinas de nível superior no programa apenas chamam as funções referenciadas por ponteiros. Posso adicionar suporte para novas variantes de hardware sem tocar nas rotinas de nível superior.

Eu costumava usar instruções switch / case para selecionar as versões de função adequadas, mas isso se tornou impraticável à medida que o programa crescia para oferecer suporte a mais e mais variantes de hardware. Tive de adicionar declarações de casos em todos os lugares.

Também tentei camadas de funções intermediárias para descobrir qual função usar, mas não ajudaram muito. Eu ainda precisava atualizar as instruções de caso em vários lugares sempre que adicionávamos uma nova variante. Com os ponteiros de função, só preciso alterar a função de inicialização.

miron-semack
fonte
3

Como Rich disse acima, é muito comum que os ponteiros de funções no Windows façam referência a algum endereço que armazena a função.

Quando você programa na C languageplataforma Windows você basicamente carrega algum arquivo DLL na memória primária (usando LoadLibrary) e para usar as funções armazenadas na DLL você precisa criar ponteiros de funções e apontar para estes endereços (usando GetProcAddress).

Referências:

Thomaz
fonte
2

Ponteiros de função podem ser usados ​​em C para criar uma interface com a qual programar. Dependendo da funcionalidade específica necessária no tempo de execução, uma implementação diferente pode ser atribuída ao ponteiro de função.

dafmetal
fonte
2

Meu principal uso deles tem sido CALLBACKS: quando você precisa salvar informações sobre uma função para chamar mais tarde .

Digamos que você esteja escrevendo Bomberman. 5 segundos após a pessoa soltar a bomba, ela deve explodir (chame a explode()função).

Agora existem 2 maneiras de fazer isso. Uma maneira é "sondar" todas as bombas na tela para ver se estão prontas para explodir no loop principal.

foreach bomb in game 
   if bomb.boomtime()
       bomb.explode()

Outra maneira é anexar um retorno de chamada ao seu sistema de relógio. Quando uma bomba é plantada, você adiciona um retorno de chamada para fazê-la chamar bomb.explode () quando chegar a hora certa .

// user placed a bomb
Bomb* bomb = new Bomb()
make callback( function=bomb.explode, time=5 seconds ) ;

// IN the main loop:
foreach callback in callbacks
    if callback.timeToRun
         callback.function()

Aqui callback.function()pode haver qualquer função , porque é um ponteiro de função.

bobobobo
fonte
A pergunta foi marcada com [C] e [C ++], e não com qualquer outra tag de idioma. Portanto, fornecer trechos de código em outro idioma está um pouco fora do assunto.
cmaster - restabelecer monica em
2

Uso de ponteiro de função

Para chamar a função dinamicamente com base na entrada do usuário. Criando um mapa de string e um ponteiro de função neste caso.

#include<iostream>
#include<map>
using namespace std;
//typedef  map<string, int (*)(int x, int y) > funMap;
#define funMap map<string, int (*)(int, int)>
funMap objFunMap;

int Add(int x, int y)
{
    return x+y;
}
int Sub(int x, int y)
{
        return x-y;
}
int Multi(int x, int y)
{
        return x*y;
}
void initializeFunc()
{
        objFunMap["Add"]=Add;
        objFunMap["Sub"]=Sub;
        objFunMap["Multi"]=Multi;
}
int main()
{
    initializeFunc();

    while(1)
    {
        string func;
        cout<<"Enter your choice( 1. Add 2. Sub 3. Multi) : ";
        int no, a, b;
        cin>>no;

        if(no==1)
            func = "Add";
        else if(no==2)
            func = "Sub";
        else if(no==3)
            func = "Multi";
        else 
            break;

        cout<<"\nEnter 2 no :";
                cin>>a>>b;

        //function is called using function pointer based on user input
        //If user input is 2, and a=10, b=3 then below line will expand as "objFuncMap["Sub"](10, 3)"
        int ret = objFunMap[func](a, b);      
        cout<<ret<<endl;
    }
    return 0;
}

Dessa forma, usamos o ponteiro de função em nossa empresa real. Você pode escrever 'n' número de função e chamá-los usando este método.

RESULTADO:

    Digite sua escolha (1. Adicionar 2. Sub 3. Multi): 1
    Digite 2 não: 2 4
    6
    Digite sua escolha (1. Adicionar 2. Sub 3. Multi): 2
    Digite 2 não: 10 3
    7
    Digite sua escolha (1. Adicionar 2. Sub 3. Multi): 3
    Digite 2 não: 3 6
    18
Pankaj Kumar Boora
fonte
2

Uma perspectiva diferente, além de outras boas respostas aqui:

Em C, você usa ponteiros de função, não funções (diretamente).

Quer dizer, você escreve funções, mas não pode manipular funções. Não há representação em tempo de execução de uma função que você possa usar. Você não pode nem chamar "uma função". Quando você escreve:

my_function(my_arg);

o que você está realmente dizendo é "realizar uma chamada para o my_functionponteiro com o argumento especificado". Você está fazendo uma chamada por meio de um ponteiro de função. Este decaimento para ponteiro de função significa que os seguintes comandos são equivalentes à chamada de função anterior:

(&my_function)(my_arg);
(*my_function)(my_arg);
(**my_function)(my_arg);
(&**my_function)(my_arg);
(***my_function)(my_arg);

e assim por diante (obrigado @LuuVinhPhuc).

Então, você já está usando ponteiros de função como valores . Obviamente, você gostaria de ter variáveis ​​para esses valores - e é aqui que entram todos os outros usos de metion: polimorfismo / personalização (como em qsort), callbacks, tabelas de salto etc.

Em C ++ as coisas são um pouco mais complicadas, já que temos lambdas e objetos com operator()e até mesmo uma std::functionclasse, mas o princípio ainda é basicamente o mesmo.

einpoklum
fonte
2
Ainda mais interessante, você pode chamar a função como (&my_function)(my_arg), (*my_function)(my_arg), (**my_function)(my_arg), (&**my_function)(my_arg), (***my_function)(my_arg)... porque funções decair para ponteiros de função
phuclv
1

Para linguagens OO, para realizar chamadas polimórficas nos bastidores (isso também é válido para C até certo ponto, eu acho).

Além disso, eles são muito úteis para injetar comportamentos diferentes em outra função (foo) em tempo de execução. Isso torna a função foo função de ordem superior. Além de sua flexibilidade, isso torna o código foo mais legível, pois permite que você extraia essa lógica extra de "if-else" dele.

Ele permite muitas outras coisas úteis em Python, como geradores, fechamentos etc.

saída padrão
fonte
0

Eu uso ponteiros de função extensivamente, para emular microprocessadores que têm opcodes de 1 byte. Uma matriz de 256 ponteiros de função é a maneira natural de implementar isso.

TonyK
fonte
0

Um uso de ponteiro de função pode ser quando não queremos modificar o código onde a função está sendo chamada (significando que a chamada pode ser condicional e sob diferentes condições, precisamos fazer diferentes tipos de processamento). Aqui, os ponteiros de função são muito úteis, uma vez que não precisamos modificar o código no local onde a função está sendo chamada. Simplesmente chamamos a função usando o ponteiro de função com os argumentos apropriados. O ponteiro de função pode ser feito para apontar para diferentes funções condicionalmente. (Isso pode ser feito em algum lugar durante a fase de inicialização). Além disso, o modelo acima é muito útil, se não estivermos em posição de modificar o código onde ele está sendo chamado (suponha que seja uma API de biblioteca que não podemos modificar). A API usa um ponteiro de função para chamar a função definida pelo usuário apropriada.

Sumit Trehan
fonte
0

Vou tentar dar uma lista um tanto abrangente aqui:

  • Retornos de chamada : personalize algumas funcionalidades (biblioteca) com código fornecido pelo usuário. O exemplo principal é qsort(), mas também é útil para lidar com eventos (como um botão chamando um retorno de chamada quando é clicado) ou necessário para iniciar um thread ( pthread_create()).

  • Polimorfismo : A vtable em uma classe C ++ nada mais é do que uma tabela de ponteiros de função. E um programa C também pode optar por fornecer uma vtable para alguns de seus objetos:

    struct Base;
    struct Base_vtable {
        void (*destruct)(struct Base* me);
    };
    struct Base {
        struct Base_vtable* vtable;
    };
    
    struct Derived;
    struct Derived_vtable {
        struct Base_vtable;
        void (*frobnicate)(struct Derived* me);
    };
    struct Derived {
        struct Base;
        int bar, baz;
    }

    O construtor de Derivedentão definiria sua vtablevariável de membro para um objeto global com as implementações da classe derivada de destructe frobnicate, e o código que precisava para destruir um struct Base*simplesmente chamaria base->vtable->destruct(base), o que chamaria a versão correta do destruidor, independente de qual classe derivada baserealmente aponta para .

    Sem ponteiros de função, o polimorfismo precisaria ser codificado com um exército de construções de switch como

    switch(me->type) {
        case TYPE_BASE: base_implementation(); break;
        case TYPE_DERIVED1: derived1_implementation(); break;
        case TYPE_DERIVED2: derived2_implementation(); break;
        case TYPE_DERIVED3: derived3_implementation(); break;
    }

    Isso se torna bastante difícil de manejar rapidamente.

  • Código carregado dinamicamente : quando um programa carrega um módulo na memória e tenta chamar seu código, ele deve passar por um ponteiro de função.

Todos os usos de ponteiros de função que vi se enquadram em uma dessas três classes amplas.

cmaster - reintegrar monica
fonte