Function pointer tutorials

Покажчики на функції забезпечують кілька надзвичайно цікавих, ефективних і витончених програмних методів. Ви можете використовувати їх, щоб замінити switch / if-оператора, реалізувати ваше власне пізніше зв'язування (late-binding) або зворотні виклики (callbacks). На жаль, ймовірно через їх складного синтаксису, в більшості комп'ютерних книг і документації до них ставляться вельми неприязно. Якщо про них і йдеться, то досить-таки коротко і поверхнево. Вони в меншій мірі схильні до помилок, ніж звичайні покажчики, ви ніколи не будете виділяти або звільняти для них пам'ять. Все, що ви повинні зробити, - це зрозуміти, чим вони є і вивчити їх синтаксис. Але майте на увазі: ви завжди повинні запитувати себе, чи дійсно ви потребуєте покажчику на функцію. Приємно реалізувати власне пізніше зв'язування, але використання існуючих структур C ++ може зробити ваш код легким для читання і зрозумілішим. Один з аспектів в разі пізнього зв'язування - час виконання: якщо ви викликаєте віртуальну функцію, ваша програма повинна визначити, яку функцію потрібно викликати. Це робиться за допомогою віртуальної таблиці (V-Table), що містить всі можливі функції. На кожен виклик витрачається деякий час, і можливо ви можете скоротити час, використовуючи покажчики на функції замість віртуальних функцій. Можливо, і немає.

Коли ви хочете викликати функцію DoIt () в певній точці, позначеної в програмі міткою, ви тільки ставите виклик функції DoIt () в точці мітки в вихідному коді. Потім ви компілюєте код, і кожен раз, коли програма доходить до мітки, викликається ваша функція. Все Ok. Але що робити, якщо ви не знаєте, в який момент часу повинна бути викликана функція? Що ви робите, коли хочете вирішити це під час виконання? Можливо, ви захочете використовувати так звану Функцію Зворотного Виклику (Callback Function), або вибрати функцію з пулу можливих функцій. Однак, ви також можете вирішити проблему, використовуючи оператор switch. де ви викликаєте функції точно так, як ви цього хочете, використовуючи різні блоки (branches). Але існує інший спосіб: використовуйте вказівник на функцію!

У наступному прикладі ми розглянемо задачу з виконання однієї з чотирьох основних арифметичних операцій. Спочатку завдання виконане з використанням оператора switch. Потім демонструється, як те ж саме може бути зроблено за допомогою покажчика на функцію. Це лише приклад, і завдання настільки проста, що я припускаю, ніхто не буде використовувати для цього покажчик на функцію ;-)

Розглядаючи синтаксис, можна виділити два типи вказівників на функції: один з них - це покажчики на звичайні функції C або статичні функції-члени C ++. Інший тип - це покажчики на нестатичні функції-члени в C ++. Основна відмінність в тому, що для всіх покажчиків на нестатичні функції-члени потрібен прихований аргумент: покажчик this на екземпляр класу. Завжди пам'ятайте: Ці два типи вказівників несумісні один з одним.

Так як покажчик на функцію ніщо інше, як змінна. він повинен бути визначений як зазвичай. У наступному прикладі ми визначаємо три покажчика на функції: pt2Function, pt2Member і pt2ConstMember. Вони вказують на функції, які приймають один аргумент типу float і два аргументи типу char і повертають int. У прикладах на C ++ передбачається, що функції, на які вказують наші покажчики, є (нестатичних) членами TMyClass.

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

Ви можете передати покажчик на функцію в якості вхідного параметра функції. Наприклад, вам це знадобиться, якщо ви хочете передати покажчик на функцію зворотного виклику. Представлений нижче код показує, як передати покажчик на функцію, яка повертає int. а приймає float і два char'а:

3.1 Введення в Концепцію Функцій Зворотних Викликів

Покажчики на функції забезпечують концепцію функцій зворотного виклику. Якщо ви не впевнені щодо того, як використовувати покажчики на функції, поверніться до розділу "Введення в Покажчики на Функції". Я спробую ввести поняття функцій зворотного виклику, використовуючи відому функцію сортування, qsort. Ця функція сортує елементи області в соответстви з призначеної для користувача сортуванням. Область може містити елементи будь-якого типу; вона передається в функцію сортування через укзатель на void. Також повинні бути передані розмір елемента і загальна кількість елементів в області. Тепер питання: як може функція сортування впорядкувати елементи без будь-якої інформації про їхній тип? Відповідь проста: функція отримує покажчик на функцію порівняння, яка приймає void-покажчики на два елементи області, порівнює елементи і повертає результат, закодований як int. Таким чином кожен раз, коли алгоритму сортування потрібно результат порівняння двох елементів, він тільки викликає функцію порівняння через покажчик функції: виконує зворотний виклик!

3.2 Як Виконати Зворотний Виклик в C?

3.3 Код приклад використання qsort

У наступному пріметре сортується масив елементів типу float.

3.4 Як Здійснити Зворотний Виклик в Статческой Функції-члені?

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

3.5 Як Здійснити Зворотний Виклик в Нестатческой Функції-члені? Підхід Обгортки (The Wrapper Approach)

Покажчики на нестатичні функції-члени відрізняються від простих укзателей на функції в C, оскільки їм потрібно передавати об'єкт класу через покажчик на void. Таким чином, прості покажчики на функції і нестатичні функції-члени мають різні і несумісні сигнатури! Якщо ви хочете звернутися до члена певного класу, ви тільки повинні в вашому коді замінити простий покажчик на функцію покажчиком на функцію-член. Але що ви можете зробити, якщо захочете викликати нестатічскій член довільного класу? Це трохи важко. Вам необхідно написати статичну функцію-член як обгортки. Статична функція-член має таку ж сигнатуру як функція в C! Потім ви приводите покажчик на об'єкт, функцію-член якого ви хочете викликати, до void * і передаєте його в обгортку в якості додаткового параметра або через глобальну змінну. Якщо ви використовуєте глобальну змінну, дуже важливо упевнитися, що вона завжди буде вказувати на правильний об'єкт! Звичайно, ви також можете передати параметри виклику для функції-члена. Обгортка призводить покажчик на void до покажчика на об'єкт відповідно¿ класу і викликає функцію-член. Нижче наводиться два приклади.

Приклад А: Покажчик, інстанціювати в класі, передається в якості додаткового параметра

Функція DoItA робить щось з об'єктами класу TClassA, який містить зворотний виклик. Таким чином покажчик на об'єкт класу TClassA і покажчик на статичну функцію-обгортку TClassA :: Wrapper_To_Call_Display передаються в DoItA. Ця обгортка є функцією зворотного виклику. Ви можете написати інші довільні класи такі ж, як TClassA і використовувати їх з DoItA, поки вони забезпечують необхідні функції. Зауваження: це рішення може бути корисним, якщо ви проектуєте власний інтерфейс зворотного виклику. Воно набагато краще ніж друге рішення, яке використовує глобальну змінну.

Приклад Б: Покажчик, інстанціювати в класі, зберігається в глобальній змінній

Функція DoItB робить щось з об'єктами класу TClassA, який містить зворотний виклик. Покажчик на статичну функцію-обгортку TClassA :: Wrapper_To_Call_Display передається в DoItA. Ця обгортка і є функція зворотного виклику. Обгортка використовує глобальну змінну void * pt2Object і виконує точне приведення її до покажчика на інстанси TClassB. Дуже важливо, що ви завжди ініціалізіруете глобальну змінну так, щоб вказати на правильний інстанси класу. Ви можете написати інші довільні класи такі ж, як TClassB і використовувати їх з DoItA, поки вони забезпечують необхідні функції. Зауваження: це рішення може виявитися корисним, якщо ви використовуєте інтерфейс зворотного виклику, який не може бути змінений. Це не дуже гарне рішення, тому чтоіспользованіе глобальної змінної дуже небезпечно і може послужити причиною серйозних помилок.

4.1 Що таке Функтори?

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

4.2 Як Імплементувати Функтори?

По-перше, вам потрібен базовий клас TFunctor який надає віртуальну функцію з ім'ям Call або віртуальний перевантажений оператор () за допомогою якого, ви зможете викликати функцію-член. Що ви віддасте перевагу, перевантажений оператор або функцію на зразок Call, - залежить від вас. Від базового класу ви успадковуєте шаблонний клас TSpecificFunctor, що інстанціюється конструктором, які приймають покажчик на об'єкт і покажчик на функцію-член. Похідний клас переопределяет функцію Call і / або оператор () базового класу: в перевизначених версії ви викликаєте функцію-член, використовуючи збережені покажчики на об'єкт і функцію-член. Якщо ви не впевнені, як іспоьзовать покажчики на функції, зверніться до мого Введенню в Покажчики на Функції.

4.3 Приклад використання функтором

У наступному прикладі ми маємо два навчальних класи, вони надають функцію звану 'Display', яка нічого не повертає (void) і вимагає в якості вхідного параметра рядок (const char *). Ми створюємо масив з двох покажчиків на TFunctor і инициализируем його двома покажчиками на TSpecificFunctor, які інкапсулюють покажчики на об'єкти і покажчики на члени TClassA і TClassB відповідно. Потім ми використовуємо масив функторів для виклику відповідних функцій-членів. Щоб виконати виклики функцій, покажчик на об'єкт не потрібно, і ви не повинні більше турбуватися про класах!

5.1 Введення в Покажчики на Функції

5.2 Зворотні Виклики і Функції

Розділ 3.5. приклад A

В даному прикладі бентежить перетворення покажчика на об'єкт класу TClassA до void * при передачі у функцію DoItA, а потім зворотне перетворення до покажчика на TClassA в функції-обгортці TClassA :: Wrapper_To_Call_Display (те ж саме відноситься і до прикладу Б). Подібні конструкції підійдуть (тому, що іншого нічого не залишається), якщо необхідно працювати з функціями типу pthread_create () (libpthread.so), які в якості вхідних параметрів приймають void *. У будь-якому випадку цей приклад буде працювати правильно тільки, якщо в DoItA дійсно передається покажчик на TClassA. Але що станеться, якщо помилково замість покажчика на TClassA буде передано щось інше? Компілятор не розпізнає помилки, ніщо не завадить перетворити до void * покажчик на int, наприклад. Для наочності я трохи змінив приклад (exampleА.cpp):
  • в клас TClassA додав два члена типу int - a і x,
  • функція TClassA :: Display виводить значення x,
  • в функції Callback_Using_Argument () створюються дві змінних, перша - об'єкт класу TClassA, інша - типу int; далі, для обох викликається функція DoItA.
Неважко здогадатися, що функція DoItA, отримавши в якості вхідного параметра покажчик на int, відпрацює, м'яко кажучи, не так як очікувалося. Замість значення TClassA :: x відобразиться невизначений набір біт, простіше кажучи - сміття. Можливе рішення даної проблеми - зробити Wrapper_To_Call_Display і DoItB шаблонними функціями (exampleB.cpp). Оскільки для int немає підходящої спеціалізації, компілятор видасть повідомлення про помилку.

Сайт управляється системою uCoz