В C ++, когда и как вы используете функцию обратного вызова?
РЕДАКТИРОВАТЬ:
Я хотел бы увидеть простой пример, чтобы написать функцию обратного вызова.
В C ++, когда и как вы используете функцию обратного вызова?
РЕДАКТИРОВАТЬ:
Я хотел бы увидеть простой пример, чтобы написать функцию обратного вызова.
Ответы:
Примечание. Большинство ответов касаются указателей на функции, что является одной из возможностей достижения логики «обратного вызова» в C ++, но на сегодняшний день, на мой взгляд, не самая благоприятная.
Что такое обратные вызовы (?) И зачем их использовать (!)
Обратный вызов - это вызываемый (см. Ниже) метод, принятый классом или функцией, который используется для настройки текущей логики в зависимости от этого обратного вызова.
Одной из причин использования обратных вызовов является написание универсального кода, который не зависит от логики в вызываемой функции и может быть повторно использован с различными обратными вызовами.
Многие функции библиотеки стандартных алгоритмов
<algorithm>
используют обратные вызовы. Например,for_each
алгоритм применяет унарный обратный вызов к каждому элементу в диапазоне итераторов:который может быть использован для того, чтобы сначала увеличить, а затем распечатать вектор, передав соответствующие вызовы, например:
который печатает
Другое применение обратных вызовов - это уведомление вызывающих абонентов об определенных событиях, что обеспечивает определенную гибкость статического времени / времени компиляции.
Лично я использую локальную библиотеку оптимизации, которая использует два различных обратных вызова:
Таким образом, разработчик библиотеки не отвечает за то, что происходит с информацией, которая предоставляется программисту посредством обратного вызова уведомления, и ему не нужно беспокоиться о том, как на самом деле определить значения функции, поскольку они предоставляются логическим обратным вызовом. Правильный подход к этим вещам является задачей пользователя библиотеки и делает библиотеку тонкой и более общей.
Кроме того, обратные вызовы могут включать динамическое поведение во время выполнения.
Представьте себе некоторый класс игрового движка, который имеет функцию, которая запускается каждый раз, когда пользователь нажимает кнопку на его клавиатуре, и набор функций, которые управляют вашим игровым поведением. С помощью обратных вызовов вы можете (пере) решить во время выполнения, какое действие будет предпринято.
Здесь функция
key_pressed
использует обратные вызовы, сохраненные в,actions
чтобы получить желаемое поведение при нажатии определенной клавиши. Если игрок решает изменить кнопку прыжка, двигатель может вызватьи, таким образом, изменить поведение вызова
key_pressed
(который вызываетplayer_jump
) после нажатия этой кнопки в следующий раз в игре.Что такое вызываемые в C ++ (11)?
См. C ++ concept: Callable на cppreference для более формального описания.
Функциональность обратного вызова может быть реализована несколькими способами в C ++ (11), поскольку несколько разных вещей могут быть вызваны * :
std::function
объектыoperator()
)* Примечание: указатель на элементы данных также может быть вызван, но функция не вызывается вообще.
Несколько важных способов написать обратные вызовы в деталях
Примечание. Начиная с C ++ 17,
f(...)
можно записать вызов like,std::invoke(f, ...)
который также обрабатывает указатель на регистр члена.1. Функциональные указатели
Указатель на функцию - это «самый простой» (с точки зрения универсальности; с точки зрения читабельности, возможно, наихудший) тип, который может иметь обратный вызов.
Давайте иметь простую функцию
foo
:1.1 Написание указателя функции / обозначения типа
Тип указателя на функцию имеет обозначение
где тип указателя на именованную функцию будет выглядеть
using
Декларация дает нам возможность сделать вещи немного более удобным для чтения, так какtypedef
дляf_int_t
также можно записать в виде:Где (по крайней мере, для меня) более понятно, что
f_int_t
это псевдоним нового типа, и распознавание типа указателя функции также легчеИ объявление функции с использованием обратного вызова типа указателя на функцию будет иметь вид:
1.2 Обратный звонок
Нотация вызова соответствует простому синтаксису вызова функции:
1.3 Обратный звонок использовать нотацию и совместимые типы
Функция обратного вызова, принимающая указатель на функцию, может быть вызвана с помощью указателей на функцию.
Использовать функцию, которая принимает обратный вызов указателя функции, довольно просто:
1.4 Пример
Можно написать функцию, которая не зависит от того, как работает обратный вызов:
где возможные обратные вызовы могут быть
используется как
2. Указатель на функцию-член
Указатель на функцию-член (некоторого класса
C
) - это особый тип (и даже более сложный) указателя на функцию, для работы с которым требуется объект типаC
.2.1 Запись указателя на функцию-член / нотацию типа
Указатель на член типа функции для некоторого класса
T
имеет обозначениегде именованный указатель на функцию-член будет - по аналогии с указателем на функцию - выглядеть следующим образом:
Пример: Объявление функции, принимающей указатель на обратный вызов функции-члена, в качестве одного из аргументов:
2.2 Обратный звонок
Указатель на функцию-член
C
может быть вызван относительно объекта типаC
с помощью операций доступа к элементу с указателем с разыменовкой. Примечание: скобки обязательны!Примечание: если указатель на
C
доступен, синтаксис эквивалентен (где указатель наC
должен быть также разыменован):2.3 Обратный звонок использовать нотацию и совместимые типы
Функция обратного вызова, принимающая указатель на функцию-член класса,
T
может быть вызвана с использованием указателя на функцию-член классаT
.Использование функции, которая принимает указатель на обратный вызов функции-члена, по аналогии с указателями на функцию также довольно просто:
3.
std::function
объекты (заголовок<functional>
)std::function
Класс является полиморфной функцией оберткой для хранения, копирования или запускайте вызываемые объекты.3.1 Написание обозначения
std::function
объекта / типаТип
std::function
объекта, хранящего вызываемый объект, выглядит следующим образом:3.2 Обратный звонок
Класс
std::function
имеетoperator()
определенный , который может быть использован для вызова своей цели.3.3 Обратный звонок использовать нотацию и совместимые типы
std::function
Обратный вызов является более общим , чем указатели на функции или указатель на функцию - член , так как различные типы могут быть переданы и неявно преобразуются вstd::function
объект.3.3.1 Указатели на функции и указатели на функции-члены
Указатель на функцию
или указатель на функцию-член
может быть использован.
3.3.2 Лямбда-выражения
Безымянное замыкание из лямбда-выражения может храниться в
std::function
объекте:3.3.3
std::bind
выраженияРезультат
std::bind
выражения может быть передан. Например, связывая параметры с вызовом указателя функции:Где также объекты могут быть связаны как объект для вызова указателя на функции-члены:
3.3.4 Функциональные объекты
Объекты классов, имеющих надлежащую
operator()
перегрузку, также могут храниться внутриstd::function
объекта.3.4 Пример
Изменение примера использования указателя на функцию
std::function
дает гораздо больше полезности для этой функции, потому что (см. 3.3) у нас больше возможностей использовать ее:
4. Шаблонный тип обратного вызова
Используя шаблоны, код, вызывающий обратный вызов, может быть даже более общим, чем использование
std::function
объектов.Обратите внимание, что шаблоны являются функцией времени компиляции и инструментом дизайна для полиморфизма времени компиляции. Если динамическое поведение во время выполнения должно быть достигнуто с помощью обратных вызовов, шаблоны помогут, но они не будут вызывать динамику во время выполнения.
4.1 Написание (обозначения типа) и вызов шаблонных обратных вызовов
Обобщение, т. Е.
std_ftransform_every_int
Код, приведенный выше, может быть достигнуто с помощью шаблонов:с еще более общим (а также самым простым) синтаксисом для типа обратного вызова, являющегося простым, подлежащим выводу шаблонным аргументом:
Примечание: включенный вывод печатает имя типа, выведенное для шаблонного типа
F
. Реализацияtype_name
дается в конце этого поста.Наиболее общая реализация для унарного преобразования диапазона является частью стандартной библиотеки, а именно
std::transform
, которая также основана на повторяющихся типах.4.2 Примеры использования шаблонных обратных вызовов и совместимых типов
Совместимые типы для
std::function
метода шаблонного обратного вызоваstdf_transform_every_int_templ
идентичны вышеупомянутым типам (см. 3.4).Однако при использовании шаблонной версии сигнатура используемого обратного вызова может немного измениться:
Примечание:
std_ftransform_every_int
(не шаблонная версия; см. Выше) работает,foo
но не используетmuh
.Простой шаблонный параметр
transform_every_int_templ
может иметь любой возможный вызываемый тип.Приведенный выше код печатает:
type_name
реализация, использованная вышеисточник
int b = foobar(a, foo); // call foobar with pointer to foo as callback
, это опечатка не так ли?foo
должен быть указатель, чтобы это работало AFAIK.[conv.func]
стандарта C ++ 11 гласит: « Значение l типа функции T может быть преобразовано в значение типа« указатель на T ». Результатом является указатель на функцию ". Это стандартное преобразование и, как таковое, происходит неявно. Можно (конечно) использовать здесь указатель на функцию.Существует также способ выполнения обратных вызовов на языке C: указатели на функции
Теперь, если вы хотите передать методы класса как обратные вызовы, объявления этих указателей на функции имеют более сложные объявления, например:
источник
typedef
использования типа обратного вызова? Это вообще возможно?typedef
это просто синтаксический сахар, чтобы сделать его более читабельным. Безtypedef
определения DoWorkObject для указателей на функции будет:void DoWorkObject(int (*callback)(float))
. Для членов указатели будут:void DoWorkObject(int (ClassName::*callback)(float))
Скотт Мейерс приводит хороший пример:
Я думаю, что пример говорит обо всем.
std::function<>
это «современный» способ написания обратных вызовов C ++.источник
Функция обратного вызова - это метод, который передается в подпрограмму и в какой-то момент вызывается подпрограммой, которой он передается.
Это очень полезно для создания программного обеспечения многократного использования. Например, многие API-интерфейсы операционной системы (например, API-интерфейс Windows) интенсивно используют обратные вызовы.
Например, если вы хотите работать с файлами в папке - вы можете вызвать функцию API со своей собственной подпрограммой, и ваша подпрограмма запускается один раз для файла в указанной папке. Это позволяет API быть очень гибким.
источник
Принятый ответ очень полезен и достаточно исчерпывающий. Тем не менее, ФП заявляет
Итак, начнем с C ++ 11,
std::function
поэтому вам не нужны указатели на функции и тому подобное:Этот пример, кстати, как-то реален, потому что вы хотите вызвать функцию
print_hashes
с различными реализациями хеш-функций, для этого я предоставил простую. Он получает строку, возвращает int (хеш-значение предоставленной строки), и все, что вам нужно запомнить из синтаксической части, -std::function<int (const std::string&)>
это описание такой функции как входной аргумент функции, которая ее вызовет.источник
В C ++ нет явного понятия функции обратного вызова. Механизмы обратного вызова часто реализуются через указатели функций, объекты функторов или объекты обратного вызова. Программисты должны явно разрабатывать и реализовывать функции обратного вызова.
Редактировать на основе обратной связи:
Несмотря на отрицательный отзыв, этот ответ получен, это не так. Я постараюсь объяснить, откуда я родом.
C и C ++ имеют все необходимое для реализации функций обратного вызова. Наиболее распространенным и тривиальным способом реализации функции обратного вызова является передача указателя на функцию в качестве аргумента функции.
Однако функции обратного вызова и указатели на функции не являются синонимами. Указатель на функцию - это языковой механизм, а функция обратного вызова - это семантическая концепция. Указатели на функции - не единственный способ реализовать функцию обратного вызова - вы также можете использовать функторы и даже виртуальные функции для различных видов сада. То, что делает функцию вызовом обратного вызова, - это не механизм, используемый для идентификации и вызова функции, а контекст и семантика вызова. Говоря о чем-то, что является функцией обратного вызова, подразумевается большее, чем обычно, разделение между вызывающей функцией и конкретной вызываемой функцией, более слабая концептуальная связь между вызывающим и вызываемым, при этом вызывающий имеет явный контроль над тем, что вызывается.
Например, документация .NET для IFormatProvider гласит, что «GetFormat - это метод обратного вызова» , даже если это просто заурядный интерфейсный метод. Я не думаю, что кто-то будет утверждать, что все вызовы виртуальных методов являются функциями обратного вызова. Что делает GetFormat методом обратного вызова, это не механика того, как он передается или вызывается, а семантика вызывающего, выбирающего, какой метод GetFormat объекта будет вызван.
Некоторые языки включают функции с явной семантикой обратного вызова, обычно связанной с событиями и обработкой событий. Например, C # имеет тип события с синтаксисом и семантикой, явно разработанными вокруг концепции обратных вызовов. В Visual Basic есть предложение Handles , которое явно объявляет метод как функцию обратного вызова при абстрагировании от концепции делегатов или указателей на функции. В этих случаях семантическая концепция обратного вызова интегрируется в сам язык.
C и C ++, с другой стороны, не встраивают семантическую концепцию функций обратного вызова почти так явно. Механизмы есть, интегрированной семантики нет. Вы можете просто реализовать функции обратного вызова, но чтобы получить что-то более сложное, включающее в себя явную семантику обратного вызова, вы должны построить это на основе того, что предоставляет C ++, например, что Qt сделал со своими сигналами и слотами .
Короче говоря, в C ++ есть то, что вам нужно для реализации обратных вызовов, часто довольно просто и тривиально с помощью указателей на функции. У него нет ключевых слов и функций, семантика которых специфична для обратных вызовов, таких как повышение , выброс , дескрипторы , событие + = и т. Д. Если вы используете язык с такими типами элементов, встроенная поддержка обратных вызовов в C ++ будет чувствовать себя кастрированным
источник
Функции обратного вызова являются частью стандарта C, следовательно, также частью C ++. Но если вы работаете с C ++, я бы предложил вместо этого использовать шаблон наблюдателя : http://en.wikipedia.org/wiki/Observer_pattern
источник
См. Определение выше, где говорится, что функция обратного вызова передается какой-то другой функции, и в какой-то момент она вызывается.
В C ++ желательно, чтобы функции обратного вызова вызывали метод классов. Когда вы делаете это, у вас есть доступ к данным участника. Если вы используете способ определения обратного вызова на языке C, вам придется указать на статическую функцию-член. Это не очень желательно.
Вот как вы можете использовать обратные вызовы в C ++. Предположим, 4 файла. Пара файлов .CPP / .H для каждого класса. Класс C1 - это класс с методом, который мы хотим вызвать. С2 обращается к методу С1. В этом примере функция обратного вызова принимает 1 параметр, который я добавил для читателей. В примере не показаны объекты, которые создаются и используются. Одним из вариантов использования этой реализации является случай, когда у вас есть один класс, который читает и хранит данные во временном пространстве, а другой - после обработки данных. С помощью функции обратного вызова для каждой прочитанной строки данных обратный вызов может затем обработать ее. Этот метод сокращает накладные расходы на временное пространство, необходимое. Это особенно полезно для запросов SQL, которые возвращают большое количество данных, которые затем должны быть обработаны.
источник
В поднимать торг signals2 позволяет подписаться общие функции - члены (без шаблонов!) И в поточно - образом.
источник
Принятый ответ является исчерпывающим, но связан с вопросом, который я просто хочу привести здесь в качестве простого примера. У меня был код, который я написал давно. я хотел пройтись по дереву по порядку (левый узел, затем корневой узел, затем правый узел), и когда бы я ни достигал одного узла, я хотел иметь возможность вызывать произвольную функцию, чтобы она могла делать все.
источник