У чому сенс покажчиків на функції?


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

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

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
Таблиця стрибків - одне з важливих її використання.
Ashish

Якби були якісь опрацьовані приклади, це отримало б мою позитивну оцінку.
Donal Fellows

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

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

@BillyONeal Тільки якщо ви жорстко дотримуєтесь визначення GoF, і це протікає яваїзми. Я б назвав std::sort«s compпараметр як стратегія
Caleth

10

«Класичним» прикладом корисності покажчиків функцій є qsort()функція бібліотеки C , яка реалізує швидке сортування. Для того, щоб бути універсальним для будь-якої та всіх структур даних, які може придумати користувач, потрібна пара порожніх покажчиків на сортувані дані та вказівник на функцію, яка вміє порівнювати два елементи цих структур даних. Це дозволяє нам створити функцію, яку ми обираємо для роботи, і насправді навіть дозволяє вибрати функцію порівняння під час виконання, наприклад, для сортування за зростанням або за спаданням.


7

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


1
Я роблю це постійно, щоб підтримувати Windows XP, і все ще використовую ласощі Windows 7. +1.
Billy ONeal

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;

Але оскільки варіативні шаблони та лямбда вже не за горами, я не впевнений, що ми будемо довго використовувати вказівники на функції у чистому коді С ++.


Те, що ви не бачите своїх покажчиків функцій, не означає, що ви їх не використовуєте. Кожного разу (якщо компілятор не може її оптимізувати) ви викликаєте віртуальну функцію, використовуєте boost bindабо functionвикористовуєте покажчики на функції. Це все одно, що сказати, що ми не використовуємо вказівники в C ++, оскільки ми використовуємо розумні вказівники. У всякому разі, я нікчемний.
Флоріан

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

5

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


2
@KennyTM: Я вказував на єдиний інший приклад цього в стандартній бібліотеці C. Наведені вами приклади є частиною сторонніх бібліотек.
Billy ONeal

5

Нещодавно я використовував покажчики функцій для створення шару абстракції.

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

Під час ініціалізації програма з’ясовує, на якому обладнанні вона працює, і заповнює покажчики функцій. Усі підпрограми вищого рівня в програмі просто викликають функції, на які посилаються вказівники. Я можу додати підтримку нових варіантів обладнання, не торкаючись процедур вищого рівня.

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

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


3

Як сказав Річ вище, дуже звично, що вказівники на функції в Windows посилаються на якусь адресу, яка зберігає функції.

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

Список літератури:


2

Покажчики функцій можуть бути використані в C для створення інтерфейсу, щодо якого можна програмувати. Залежно від конкретної функціональності, яка необхідна під час виконання, вказівнику функції може бути призначена інша реалізація.


2

Моїм основним використанням були CALLBACKS: коли вам потрібно зберегти інформацію про функцію, яку потрібно викликати пізніше .

Скажімо, ви пишете 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 стає більш читабельним, оскільки він дозволяє вам витягнути з нього додаткову логіку "якщо-ще".

Це дозволяє багато інших корисних речей у Python, таких як генератори, закриття тощо.


0

Я широко використовую покажчики функцій для емуляції мікропроцесорів, які мають 1-байтові коди операцій. Масив із 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;
    }

    Потім конструктор Derivedbi встановив для своєї vtableзмінної-члена глобальний об'єкт з реалізаціями похідного класу destructі frobnicate, а код, який потрібно було знищити 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.