В чем смысл указателей на функции?


94

Мне сложно увидеть полезность указателей на функции. Я предполагаю, что в некоторых случаях это может быть полезно (в конце концов, они существуют), но я не могу придумать случая, когда лучше или неизбежно использовать указатель на функцию.

Не могли бы вы привести пример правильного использования указателей на функции (на C или C ++)?


1
Вы можете найти довольно много обсуждения указателей функций в этом связанном вопросе SO .
itsmatt

20
@itsmatt: Не совсем. "Как работает телевизор?" это совсем другой вопрос, чем "Что мне делать с телевизором?"
sbi

6
В C ++ вы, вероятно, вместо этого использовали бы функтор ( en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B ).
kennytm

11
В старые темные времена, когда C ++ был «скомпилирован» для C, вы могли действительно видеть, как реализованы виртуальные методы - да, с указателями на функции.
sbk 07

1
Очень важно, если вы хотите использовать C ++ с управляемым C ++ или C #, то есть: делегаты и обратные вызовы
Maher

Ответы:


108

Большинство примеров сводятся к обратным вызовам : вы вызываете функцию, f()передавая адрес другой функции g(), и f()вызываете g()какую-то конкретную задачу. Если вместо этого вы передадите f()адрес h(), то вместо этого f()перезвонит h().

По сути, это способ параметризации функции: некоторая часть ее поведения жестко не закодирована f(), а встроена в функцию обратного вызова. Вызывающие абоненты могут f()вести себя по-разному, передавая разные функции обратного вызова. Классический вариант взят qsort()из стандартной библиотеки C, в которой критерий сортировки используется как указатель на функцию сравнения.

В C ++ это часто делается с помощью объектов функций (также называемых функторами). Это объекты, которые перегружают оператор вызова функции, поэтому вы можете вызывать их, как если бы они были функцией. Пример:

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

functor f;
f(42);

Идея заключается в том, что, в отличие от указателя на функцию, объект функции может нести не только алгоритм, но и данные:

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);

Еще одно преимущество состоит в том, что иногда проще встроить вызовы объектов функций, чем вызовы через указатели функций. По этой причине сортировка в C ++ иногда выполняется быстрее, чем в C.


1
+1, также см. Этот ответ для другого примера: stackoverflow.com/questions/1727824/…
sharptooth

Вы забыли виртуальные функции, по сути, они также являются указателями на функции (в сочетании со структурой данных, которую генерирует компилятор). Более того, в чистом C вы можете сами создавать эти структуры для написания объектно-ориентированного кода, как это видно на уровне VFS (и во многих других местах) ядра Linux.
Флориан

2
@krynr: Виртуальные функции - это указатели функций только для разработчиков компилятора, и если вам нужно спросить, для чего они подходят, вам, вероятно (надеюсь!), вряд ли понадобится реализовать механизм виртуальных функций компилятора.
sbi

@sbi: Вы, конечно, правы. Однако я думаю, что это помогает понять, что происходит внутри абстракции. Кроме того, реализация вашей собственной vtable на C и написание объектно-ориентированного кода дает действительно хороший опыт обучения.
Флориан

Брауни указывает на то, что дает ответ на жизнь, вселенную и все остальное, а также то, о чем просил OP
simplename 05

41

Ну, я обычно использую их (профессионально) в таблицах переходов (см. Также этот вопрос на StackOverflow ).

Таблицы переходов обычно (но не исключительно) используются в конечных автоматах для управления данными. Вместо вложенного переключателя / корпуса

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

вы можете создать двумерный массив указателей на функции и просто вызвать handleEvent[state][event]


24

Примеры:

  1. Пользовательская сортировка / поиск
  2. Различные шаблоны (например, стратегия, наблюдатель)
  3. Обратные вызовы

1
Таблица для прыжков - одно из важных ее применений.
Ашиш

Если бы были какие-то работающие примеры, я бы за это проголосовал.
Donal Fellows

1
Стратегию и наблюдатель, вероятно, лучше реализовать с помощью виртуальных функций, если доступен C ++. В противном случае +1.
Билли Онил

Я думаю, что разумное использование указателей на функции может сделать наблюдателя более компактным и легким,
Андрей

@BillyONeal Только если вы строго придерживаетесь определения GoF, через которое просачиваются Javaизмы. Я бы назвал std::sort«s compпараметр как стратегия
Caleth

10

«Классическим» примером полезности указателей на функции является qsort()функция библиотеки C , которая реализует быструю сортировку. Чтобы быть универсальным для любых структур данных, которые может придумать пользователь, требуется пара пустых указателей на сортируемые данные и указатель на функцию, которая знает, как сравнивать два элемента этих структур данных. Это позволяет нам создать нашу функцию выбора для задания и фактически даже позволяет выбирать функцию сравнения во время выполнения, например, для сортировки по возрастанию или убыванию.


7

Согласитесь со всем вышеперечисленным, плюс .... Когда вы загружаете dll динамически во время выполнения, вам потребуются указатели на функции для вызова функций.


1
Я делаю это все время, чтобы поддерживать Windows XP, и до сих пор использую полезности Windows 7. +1.
Билли Онил

7

Я собираюсь пойти против течения здесь.

В C указатели на функции - единственный способ реализовать настройку, потому что нет объектно-ориентированного программирования.

В C ++ для достижения одного и того же результата можно использовать указатели на функции или функторы (объекты функций).

Функторы имеют ряд преимуществ перед необработанными указателями на функции в связи с их объектной природой, а именно:

  • Они могут представлять несколько перегрузок operator()
  • Они могут иметь состояние / ссылку на существующие переменные
  • Их можно построить на месте ( lambdaи bind)

Я лично предпочитаю функторы указателям на функции (несмотря на шаблонный код), в основном потому, что синтаксис для указателей на функции может легко запутаться (из Учебника по указателям на функции ):

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

Единственный раз, когда я видел, как указатели на функции используются там, где функторы не могут, был в Boost.Spirit. Они полностью злоупотребили синтаксисом, чтобы передать произвольное количество параметров как один параметр шаблона.

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

Но поскольку вариативные шаблоны и лямбды не за горами, я не уверен, что мы будем долго использовать указатели на функции в чистом коде C ++.


То, что вы не видите указателей на свои функции, не означает, что вы их не используете. Каждый раз (если компилятор не может оптимизировать его) вы вызываете виртуальную функцию, используете boost bindили functionуказатели на функции. Это как сказать, что мы не используем указатели в C ++, потому что мы используем интеллектуальные указатели. Во всяком случае, я придираюсь.
Флориан

3
@krynr: Я вежливо не согласен. Важно то, что вы видите и набираете , это синтаксис, который вы используете. Для вас не имеет значения, как все это работает за кулисами: в этом суть абстракции .
Matthieu M.

5

В C классическим использованием является функция qsort , где четвертым параметром является указатель на функцию, используемую для выполнения упорядочивания в сортировке. В C ++ для такого рода вещей обычно используются функторы (объекты, похожие на функции).


2
@KennyTM: Я указывал на единственный другой экземпляр этого в стандартной библиотеке C. Приведенные вами примеры являются частью сторонних библиотек.
Билли Онил,

5

Недавно я использовал указатели на функции, чтобы создать уровень абстракции.

У меня есть программа, написанная на чистом C, которая работает во встроенных системах. Он поддерживает несколько вариантов оборудования. В зависимости от оборудования, на котором я работаю, ему необходимо вызывать разные версии некоторых функций.

Во время инициализации программа определяет, на каком оборудовании она работает, и заполняет указатели функций. Все подпрограммы более высокого уровня в программе просто вызывают функции, на которые ссылаются указатели. Я могу добавить поддержку новых вариантов оборудования, не затрагивая процедуры более высокого уровня.

Раньше я использовал операторы switch / case для выбора правильных версий функций, но это стало непрактичным, поскольку программа росла для поддержки все большего количества вариантов оборудования. Мне приходилось добавлять операторы case повсюду.

Я также пробовал использовать промежуточные функциональные уровни, чтобы выяснить, какую функцию использовать, но они не очень помогли. Мне все еще приходилось обновлять операторы case в нескольких местах, когда мы добавляли новый вариант. С указателями функций мне нужно изменить только функцию инициализации.


3

Как сказал Ричард выше, указатели функций в Windows обычно ссылаются на какой-либо адрес, в котором хранится функция.

При программировании C languageна платформе Windows вы в основном загружаете какой-то файл DLL в основную память (используя LoadLibrary), а для использования функций, хранящихся в DLL, вам необходимо создать указатели функций и указать на этот адрес (используя GetProcAddress).

Ссылки:


2

Указатели функций могут использоваться в C для создания интерфейса для программирования. В зависимости от конкретной функциональности, необходимой во время выполнения, указателю функции может быть назначена другая реализация.


2

Я использовал их в основном для ОТВЕТОВ: когда вам нужно сохранить информацию о функции для последующего вызова .

Допустим, вы пишете Bomberman. Через 5 секунд после того, как человек сбросит бомбу, она должна взорваться (вызовите explode()функцию).

Теперь есть 2 способа сделать это. Один из способов - «исследовать» все бомбы на экране, чтобы увидеть, готовы ли они взорваться в основном цикле.

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

Другой способ - прикрепить обратный вызов к вашей системе часов. Когда бомба закладывается, вы добавляете обратный вызов, чтобы он вызвал bomb.explode (), когда придет время .

// 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()

Здесь callback.function()может быть любая функция , потому что это указатель на функцию.


Вопрос был помечен тегами [C] и [C ++], а не тегами других языков. Таким образом, предоставление фрагментов кода на другом языке немного не по теме.
cmaster - восстановить Монику

2

Использование указателя функции

Для динамического вызова функции на основе пользовательского ввода. В этом случае создав карту строки и указателя на функцию.

#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;
}

Таким образом, мы использовали указатель на функцию в нашей фактической балансовой единице. Вы можете написать n номеров функций и вызывать их с помощью этого метода.

ВЫВОД:

    Введите свой выбор (1. Добавить 2. Дополнительный 3. Мульти): 1
    Введите 2 №: 2 4
    6
    Введите свой выбор (1. Добавить 2. Дополнительный 3. Мульти): 2
    Введите 2 №: 10 3
    7
    Введите свой выбор (1. Добавить 2. Дополнительный 3. Мульти): 3
    Введите 2 №: 3 6
    18

2

Другая точка зрения, помимо других хороших ответов здесь:

В C вы используете только указатели на функции, а не (напрямую) функции.

Я имею в виду, что вы пишете функции, но не можете манипулировать функциями. Не существует представления функции как таковой, которую вы могли бы использовать во время выполнения. Вы даже не можете назвать «функцию». Когда вы пишете:

my_function(my_arg);

на самом деле вы говорите «выполнить вызов my_functionуказателя с указанным аргументом». Вы делаете вызов через указатель функции. Этот переход на указатель функции означает, что следующие команды эквивалентны предыдущему вызову функции:

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

и так далее (спасибо @LuuVinhPhuc).

Итак, вы уже используете указатели на функции в качестве значений . Очевидно, вы захотите иметь переменные для этих значений - и здесь могут быть задействованы все другие виды использования: полиморфизм / настройка (как в qsort), обратные вызовы, таблицы переходов и т. Д.

В C ++ все немного сложнее, так как у нас есть лямбды, объекты с operator()и даже std::functionкласс, но принцип все тот же.


2
еще более интересно, вы можете вызвать функцию , как (&my_function)(my_arg), (*my_function)(my_arg), (**my_function)(my_arg), (&**my_function)(my_arg), (***my_function)(my_arg)... , потому что функции распадаются на указатели на функции
phuclv

1

Для языков OO - для выполнения полиморфных вызовов за кулисами (я полагаю, что до некоторой степени это также верно для C).

Более того, они очень полезны для внедрения другого поведения в другую функцию (foo) во время выполнения. Это делает функцию foo функцией высшего порядка. Помимо гибкости, это делает код foo более читабельным, так как позволяет вытащить из него дополнительную логику «if-else».

Он позволяет использовать множество других полезных вещей в Python, таких как генераторы, замыкания и т. Д.


0

Я широко использую указатели на функции для эмуляции микропроцессоров с однобайтовыми кодами операций. Массив из 256 указателей на функции - естественный способ реализовать это.


0

Одно из применений указателя на функцию может заключаться в том, что мы, возможно, не захотим изменять код, в котором вызывается функция (что означает, что вызов может быть условным, и при разных условиях нам нужно выполнять другой вид обработки). Здесь указатели на функции очень удобны, поскольку нам не нужно изменять код в том месте, где функция вызывается. Мы просто вызываем функцию, используя указатель функции с соответствующими аргументами. Указатель функции может указывать на разные функции условно. (Это можно сделать где-нибудь на этапе инициализации). Более того, описанная выше модель очень полезна, если мы не в состоянии изменить код, в котором она вызывается (предположим, что это API библиотеки, который мы не можем изменить). API использует указатель функции для вызова соответствующей пользовательской функции.


0

Я постараюсь дать здесь несколько исчерпывающий список:

  • Обратные вызовы : настройте некоторые (библиотечные) функции с помощью кода, предоставленного пользователем. Ярким примером является qsort(), но он также полезен для обработки событий (например, кнопки, вызывающей обратный вызов при нажатии) или необходимости для запуска потока ( pthread_create()).

  • Полиморфизм : vtable в классе C ++ - это не что иное, как таблица указателей на функции. И программа на C также может предоставить vtable для некоторых своих объектов:

    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;
    }

    Конструктор Derivedзатем установит свою vtableпеременную-член в глобальный объект с реализациями производного класса destructи frobnicate, а код, необходимый для разрушения a struct Base*, просто вызовет base->vtable->destruct(base), который вызовет правильную версию деструктора, независимо от того, какой производный классbase фактически указывает на .

    Без указателей на функции полиморфизм нужно было бы закодировать с помощью целой армии конструкций переключателей, таких как

    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;
    }

    Это довольно быстро становится довольно громоздким.

  • Динамически загружаемый код : когда программа загружает модуль в память и пытается вызвать его код, она должна пройти через указатель функции.

Все виды использования указателей на функции, которые я видел, попадают в один из этих трех широких классов.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.