При реализации функции обратного вызова в C ++ я все еще должен использовать указатель на функцию в стиле C:
void (*callbackFunc)(int);
Или я должен использовать std :: function:
std::function< void(int) > callbackFunc;
При реализации функции обратного вызова в C ++ я все еще должен использовать указатель на функцию в стиле C:
void (*callbackFunc)(int);
Или я должен использовать std :: function:
std::function< void(int) > callbackFunc;
Ответы:
Короче говоря, используйте,std::function
если у вас нет причин не делать этого.
Недостатком указателей на функции является невозможность захвата некоторого контекста. Вы не сможете, например, передать лямбда-функцию в качестве обратного вызова, которая захватывает некоторые переменные контекста (но она будет работать, если она не захватывает какие-либо). Вызов переменной-члена объекта (то есть нестатического), таким образом, также невозможен, так как объект ( this
-pointer) должен быть захвачен. (1)
std::function
(поскольку C ++ 11) предназначен главным образом для хранения функции (для ее передачи не требуется ее сохранения). Следовательно, если вы хотите сохранить обратный вызов, например, в переменной-члене, это, вероятно, ваш лучший выбор. Но также, если вы не сохраняете его, это хороший «первый выбор», хотя он имеет недостаток, заключающийся в том, что при вызове возникают некоторые (очень маленькие) издержки (так что в очень критичной для производительности ситуации это может быть проблемой, но в большинстве это не должно). Он очень «универсален»: если вы заботитесь о последовательном и читабельном коде, а также не хотите думать о каждом своем выборе (т.е. хотите сделать его простым), используйте его std::function
для каждой передаваемой функции.
Подумайте о третьем варианте: если вы собираетесь реализовать небольшую функцию, которая затем сообщает о чем-либо через предоставленную функцию обратного вызова, рассмотрите параметр шаблона , который затем может быть любым вызываемым объектом , то есть указателем на функцию, функтором, лямбда-выражением, a std::function
, ... Недостатком здесь является то, что ваша (внешняя) функция становится шаблоном и, следовательно, должна быть реализована в заголовке. С другой стороны, вы получаете преимущество, заключающееся в том, что вызов обратного вызова может быть встроенным, так как клиентский код вашей (внешней) функции «видит» вызов обратного вызова, когда будет доступна точная информация о типе.
Пример для версии с параметром шаблона (напишите &
вместо &&
pre-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 ++ (и, кстати, интенсивно используется в WIN32 API), но его следует избегать, потому что у нас есть лучшие варианты в C ++.
(2) Если мы не говорим о шаблоне класса, то есть класс, в котором вы храните функцию, является шаблоном. Но это будет означать, что на стороне клиента тип функции определяет тип объекта, в котором хранится обратный вызов, что практически никогда не используется в реальных случаях использования.
(3) Для pre-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 или для создания интерфейсов C для связи между сгенерированным кодом различными компиляторами. Я также использовал их в качестве внутренних деталей реализации, когда я реализую таблицы переходов, стирание типов и т. Д., Когда я и создаю, и использую его, и не выставляю его внешне для использования любым клиентским кодом, а указатели функций делают все, что мне нужно ,
Обратите внимание, что вы можете написать оболочки, которые превращают a std::function<int(int)>
в int(void*,int)
стиль обратного вызова, при условии, что имеется надлежащая инфраструктура управления временем жизни обратного вызова. Так что, как тест на дым для любой системы управления временем жизни обратного вызова в стиле C, я хотел бы убедиться, что упаковка std::function
работает достаточно хорошо.
void*
взялось? Почему вы хотите поддерживать состояние за пределами функции? Функция должна содержать весь необходимый код, всю функциональность, вы просто передаете ей нужные аргументы, изменяете и возвращаете что-то. Если вам нужно какое-то внешнее состояние, тогда зачем functionPtr или callback нести этот багаж? Я думаю, что обратный вызов неоправданно сложен.
this
. Это потому, что нужно учитывать случай вызова функции-члена, поэтому нам нужен this
указатель, указывающий на адрес объекта? Если я ошибаюсь, не могли бы вы дать мне ссылку, где я могу найти больше информации об этом, потому что я не могу найти много об этом. Заранее спасибо.
void*
для разрешения передачи состояния времени выполнения. Указатель на функцию с аргументом a void*
и void*
аргументом может эмулировать вызов функции-члена для объекта. Извините, я не знаю ресурса, который проходит через "проектирование механизмов обратного вызова C 101".
this
. Это то, что я имел в виду. Хорошо, спасибо в любом случае.
Используйте std::function
для хранения произвольных вызываемых объектов. Это позволяет пользователю предоставлять любой контекст, необходимый для обратного вызова; простой указатель на функцию - нет.
Если вам по какой-то причине нужно использовать простые указатели функций (возможно, потому, что вы хотите C-совместимый API), то вам следует добавить void * user_context
аргумент, чтобы он по крайней мере (хотя и неудобно) мог получить доступ к состоянию, которое напрямую не передается в функция.
Единственная причина, чтобы избежать std::function
- это поддержка устаревших компиляторов, в которых отсутствует поддержка этого шаблона, который был представлен в C ++ 11.
Если поддержка языка, предшествующего C ++ 11, не является обязательным требованием, использование std::function
предоставляет вызывающим абонентам больший выбор при реализации обратного вызова, что делает его более подходящим вариантом по сравнению с «простыми» указателями на функции. Он предлагает пользователям вашего API более широкий выбор, в то же время абстрагируясь от особенностей их реализации для вашего кода, который выполняет обратный вызов.