Чи реалізувати функцію зворотного дзвінка в C ++, чи повинен я все-таки використовувати вказівник функції C:
void (*callbackFunc)(int);
Або я повинен використовувати std :: функцію:
std::function< void(int) > callbackFunc;
Чи реалізувати функцію зворотного дзвінка в C ++, чи повинен я все-таки використовувати вказівник функції C:
void (*callbackFunc)(int);
Або я повинен використовувати std :: функцію:
std::function< void(int) > callbackFunc;
Відповіді:
Словом, використовуйте,std::function
якщо у вас немає причин цього не робити.
Покажчики функцій мають недолік у тому, що вони не в змозі захопити певний контекст. Наприклад, ви не зможете передавати лямбда-функцію як зворотний виклик, який фіксує деякі змінні контексту (але він буде працювати, якщо не зафіксує жодної). Викликати змінну члена об'єкта (тобто нестатичну), таким чином, також неможливо, оскільки об'єкт ( this
-показник) потрібно захопити. (1)
std::function
(оскільки C ++ 11) - це насамперед для зберігання функції (для передачі її навколо не потрібно зберігати). Отже, якщо ви хочете зберегти зворотний виклик, наприклад, у змінній члена, це, мабуть, ваш найкращий вибір. Але також якщо ви не зберігаєте його, це хороший "перший вибір", хоча у нього є недолік введення деяких (дуже маленьких) накладних витрат при виклику (так що в дуже критичній продуктивності ситуація може бути проблемою, але в більшості не повинно). Це дуже "універсально": якщо ви багато піклуєтесь про послідовний і читабельний код, а також не хочете думати про кожен свій вибір (тобто хочете зробити його простим), використовуйте std::function
для кожної функції, яку ви проходите навколо.
Подумайте про третій варіант: якщо ви збираєтесь реалізувати невелику функцію, яка потім щось повідомляє через надану функцію зворотного виклику, розгляньте параметр шаблону , який може бути будь-яким об'єктом , що може викликати , тобто вказівником функції, функтором, лямбда, a std::function
, ... Недолік тут полягає в тому, що ваша (зовнішня) функція стає шаблоном і тому її потрібно реалізувати в заголовку. З іншого боку, ви отримуєте перевагу в тому, що виклик до зворотного дзвінка може бути вкладений, оскільки клієнтський код вашої (зовнішньої) функції "бачить" виклик до зворотного дзвінка буде точною інформацією про тип.
Приклад для версії з параметром шаблону (запишіть &
замість &&
попереднього C ++ 11):
template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
...
callback(...);
...
}
Як видно з наступної таблиці, всі вони мають свої переваги та недоліки:
+-------------------+--------------+---------------+----------------+
| | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture | no(1) | yes | yes |
| context variables | | | |
+-------------------+--------------+---------------+----------------+
| no call overhead | yes | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be inlined | no | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be stored | yes | yes | no(2) |
| in class member | | | |
+-------------------+--------------+---------------+----------------+
| can be implemented| yes | yes | no |
| outside of header | | | |
+-------------------+--------------+---------------+----------------+
| supported without | yes | no(3) | yes |
| C++11 standard | | | |
+-------------------+--------------+---------------+----------------+
| nicely readable | no | yes | (yes) |
| (my opinion) | (ugly type) | | |
+-------------------+--------------+---------------+----------------+
(1) Для вирішення цього обмеження існують обхідні шляхи, наприклад, передача додаткових даних у якості додаткових параметрів вашій (зовнішній) функції: myFunction(..., callback, data)
викликає callback(data)
. Це "зворотний виклик з аргументами" у стилі C, який можливий у C ++ (і, до речі, широко використовується в API WIN32), але його слід уникати, оскільки в C ++ є кращі варіанти.
(2) Якщо ми не говоримо про шаблон класу, тобто клас, у якому ви зберігаєте функцію, є шаблон. Але це означало б, що на стороні клієнта тип функції визначає тип об'єкта, який зберігає зворотний виклик, що майже ніколи не є варіантом для реальних випадків використання.
(3) Для попереднього C ++ 11 використовуйте boost::function
std::function
тип, якщо вам потрібно негайно зберігати її за межами називається контекстом).
void (*callbackFunc)(int);
це може бути функцією зворотного дзвінка в стилі C, але це погано непридатний варіант поганого дизайну.
Добре розроблений зворотний виклик у стилі C void (*callbackFunc)(void*, int);
- він має void*
дозволити коду, який робить зворотний виклик, підтримувати стан поза функцією. Якщо цього не зробити, змушує абонента зберігати стан в усьому світі, що є нечесним.
std::function< int(int) >
в кінцевому рахунку є трохи дорожчим, ніж int(*)(void*, int)
виклик у більшості реалізацій. Однак деякі компілятори складніше надати назви. Є std::function
реалізація клонів, які конкурують накладні функції виклику функцій конкурента (див. "Найшвидші можливі делегати" тощо), які можуть пробитися в бібліотеки.
Тепер клієнтам системи зворотного дзвінка часто потрібно налаштовувати ресурси та розпоряджатися ними під час створення та видалення зворотного дзвінка, а також знати про термін служби зворотного дзвінка. void(*callback)(void*, int)
не забезпечує цього.
Іноді це доступно через структуру коду (зворотний виклик має обмежений термін експлуатації) або через інші механізми (відреєстрація зворотних дзвінків тощо).
std::function
забезпечує засіб для управління обмеженим терміном експлуатації (остання копія об'єкта відходить, коли він забутий).
Взагалі я б використовував, std::function
якщо не маніфест щодо продуктивності. Якби це було, я спершу шукаю структурні зміни (замість зворотного зворотного дзвінка за піксель, як щодо генерування процесора сканування на основі лямбда, який ви передаєте мені?), Чого має бути достатньо, щоб зменшити накладні виклики функцій до тривіальних рівнів. ). Тоді, якщо це не триває, я delegate
списав би найшвидших делегатів і побачив, чи не піде проблема з продуктивністю.
Я здебільшого використовую лише покажчики функцій для застарілих API або для створення інтерфейсів С для спілкування між кодом, що генерується різними компіляторами. Я також використовував їх як детальну інформацію про внутрішню реалізацію, коли я впроваджую таблиці стрибків, стираю тип тощо: коли я створюю і споживаю його, і не піддаю його зовнішньому використанню будь-якого коду клієнта, а покажчики функцій роблять все, що мені потрібно .
Зауважте, що ви можете писати обгортки, які перетворюються std::function<int(int)>
на int(void*,int)
стиль зворотного виклику, якщо припустити, що існує відповідна інфраструктура управління зворотним викликом. Отже, як тест на задимлення для будь-якої системи управління зворотним викликом у стилі C, я би переконався, що завершення std::function
роботи працює досить добре.
void*
взялося? Чому ви хочете підтримувати стан поза функцією? Функція повинна містити весь необхідний їй код, всю функціональність, ви просто передаєте їй потрібні аргументи та щось змінюєте та повертаєте. Якщо вам потрібен зовнішній стан, то чому функція Ptr або зворотний виклик несе цей багаж? Я думаю, що зворотний виклик є надмірно складним.
this
. Це тому, що треба враховувати випадок виклику функції-члена, тому нам потрібен this
вказівник для вказівки на адресу об’єкта? Якщо я помиляюся, ви можете надіслати мені посилання, де я можу знайти більше інформації про це, тому що я не можу багато про це знайти. Заздалегідь спасибі.
void*
щоб дозволити передачу стану виконання. Покажчик функції з аргументом void*
та void*
аргументом може емулювати виклик функції члена об’єкту. Вибачте, я не знаю про ресурс, який проходить через "проектування механізмів зворотного виклику C 101".
this
. Це я мав на увазі. Добре, спасибі все одно.
Використовуйте std::function
для зберігання довільних об'єктів, що дзвоняться. Це дозволяє користувачеві надати будь-який контекст, необхідний для зворотного дзвінка; звичайний покажчик функції не робить.
Якщо вам потрібно з якихось причин використовувати звичайні функціональні покажчики (можливо, тому, що вам потрібен C-сумісний API), тоді вам слід додати void * user_context
аргумент, щоб принаймні можливий (хоч і незручний) для нього доступ до стану, який не передається безпосередньо в функція.
Єдина причина цього уникати std::function
- це підтримка застарілих компіляторів, у яких відсутня підтримка цього шаблону, що було введено в C ++ 11.
Якщо підтримка мови попереднього C ++ 11 не є вимогою, використання std::function
надає вашим абонентам більший вибір у реалізації зворотного дзвінка, що робить його кращим варіантом порівняно з покажчиками функцій "звичайний". Він пропонує користувачам вашого API більше вибору, одночасно абстрагуючи особливості їх застосування для вашого коду, який виконує зворотний виклик.