2014-09-15 1 views
103

При реализации функции обратного вызова в C++, должен ли я использовать функцию C-типа указатель:Должен ли я использовать std :: function или указатель на функции в C++?

void (*callbackFunc)(int); 

Или я должен использовать станд :: функции:

std::function< void(int) > callbackFunc; 
+7

Если функция обратного вызова известна во время компиляции, рассмотрите вместо этого шаблон. –

+3

Когда ** реализует ** функцию обратного вызова, вы должны делать все, что требуется вызывающему. Если ваш вопрос действительно связан с ** проектированием ** интерфейса обратного вызова, здесь нет почти достаточной информации, чтобы ответить на него. Что вы хотите, чтобы получатель вашего обратного вызова сделал? Какую информацию вам нужно передать получателю? Какая информация должна быть возвращена получателю в результате вызова? –

ответ

128

Одним словом, используйте std::function, если у вас нет оснований для этого.

Указатели указателей имеют недостаток неспособность захватить некоторый контекст. Вы не сможете, например, передать лямбда-функцию в качестве обратного вызова, который захватывает некоторые переменные контекста (но он будет работать, если он не фиксирует какой-либо). Таким образом, вызов элементарной переменной объекта (т. Е. Нестатический) также невозможен, поскольку объект (-pointer) должен быть захвачен. (1)

std::function (так как C++, 11) в первую очередь для магазин функция (передавая ее вокруг этого не требует, чтобы быть сохранены). Следовательно, если вы хотите сохранить обратный вызов, например, в переменной-члене, это, вероятно, ваш лучший выбор. Но также, если вы его не храните, это хороший «первый выбор», хотя у него есть недостаток в том, что он вводит некоторые (очень маленькие) накладные расходы при вызове (поэтому в очень критичной для производительности ситуации это может быть проблемой, но в большинстве случаев он не должен). Это очень «универсально»: если вы много заботитесь о совместимом и читаемом коде, а также не хотите думать о каждом выбранном вами выборе (т. Е. Хотите, чтобы это было просто), используйте для каждой функции, которую вы проходите, используйте std::function.

Подумайте о том, третьем варианте: Если вы собираетесь реализовать небольшую функцию, которая затем сообщает что-то с помощью прилагаемого функции обратного вызова, рассмотрит параметр шаблона, который затем может быть любой вызываемый объектом, т.е. функции указатель, функтор, лямбда, 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++ (и, кстати, сильно используется в WIN32 API), но его следует избегать, потому что у нас есть лучшие варианты на C++.

(2) Если мы не говорим о шаблоне класса, то есть классе, в котором вы храните эту функцию, является шаблон. Но это будет означать, что на стороне клиента тип функции определяет тип объекта, который хранит обратный вызов, который практически никогда не является вариантом для фактических вариантов использования.

(3) Для предварительного C++ 11, используют boost::function

+7

указатели на функции имеют накладные расходы по сравнению с параметрами шаблона. параметры шаблона делают простую инкрустацию, даже если вы переданы вниз на 15 уровней, потому что исполняемый код описывается типом параметра, а не значением. Объекты функции шаблона, хранящиеся в типах возвращаемых шаблонов, являются общим и полезным шаблоном (с хорошим конструктором копирования вы можете создать эффективную функцию шаблона invokable, которая может быть преобразована в тип std :: function-erased, если вам нужно чтобы сохранить его за пределами контекста, называемого так называемым контекстом). – Yakk

+0

Может быть, добавить строку в таблицу, показывающую, в каком стандарте был доступен метод, чтобы люди мгновенно отображали эту информацию? –

+1

@tohecz Теперь я упоминаю, требуется ли C++ 11 или нет. – leemes

13

Единственная причина избегать std::function - это поддержка устаревших компиляторов, которые не поддерживают этот шаблон, который был введен в C++ 11.

Если поддержка языка pre-C++ 11 не является обязательным требованием, использование std::function дает вашим абонентам больше возможностей для реализации обратного вызова, что делает его лучшим вариантом по сравнению с «обычными» указателями на функции. Он предлагает пользователям вашего API больше выбора, абстрагируя особенности их реализации для вашего кода, который выполняет обратный вызов.

16

Используйте std::function для хранения произвольных вызываемых объектов. Это позволяет пользователю предоставлять любой контекст для обратного вызова; указатель простой функции не работает.

Если вам почему-то нужно использовать простые указатели функций (возможно, потому, что вы хотите использовать C-совместимый API), тогда вы должны добавить аргумент void * user_context, чтобы он, по крайней мере, был (хотя и неудобным) для доступа к состоянию, не передается непосредственно функции.

20

void (*callbackFunc)(int); может быть функцией обратного вызова типа С, но это ужасно непригодным один из плохого дизайна.

Хорошо спроектированный обратный вызов C-стиля выглядит как void (*callbackFunc)(void*, int); - он имеет код void*, чтобы разрешить код, который выполняет обратный вызов, для поддержания состояния за пределами функции. Не делать этого заставляет вызывающего абонента хранить состояние глобально, что невежливо.

std::function< int(int) > заканчивается тем, что немного дороже, чем int(*)(void*, int) invokation в большинстве реализаций. Однако некоторые компиляторы сложнее. Существуют реализационные клоны std::function, которые конкурируют с накладными расходами указателей на функцию (см. «Наиболее быстрые возможные делегаты» и т. Д.), Которые могут проникнуть в библиотеки.

Теперь клиентам системы обратного вызова часто приходится настраивать ресурсы и удалять их, когда обратный вызов создается и удаляется, и знать время жизни обратного вызова. void(*callback)(void*, int) не предусматривает этого.

Иногда это можно получить через структуру кода (обратный вызов имеет ограниченный срок службы) или через другие механизмы (отменить регистрацию обратных вызовов и т.п.).

std::function обеспечивает средства для ограниченного срока службы (последняя копия объекта уходит, когда его забывают).

В общем, я использовал бы std::function, если не будет проявлена ​​работа. Если бы они это сделали, я бы сначала искал структурные изменения (вместо обратного вызова на пиксель, как насчет генерации процессора scanline, основанного на том, что вы пропустили мне лямбду?), Что должно быть достаточно, чтобы сократить накладные расходы функции на тривиальные уровни.). Затем, если это будет продолжаться, я напишу delegate на основе самых быстрых возможных делегатов и посмотрю, пропадет ли проблема с производительностью.

Я бы использовал только указатели функций для устаревших API-интерфейсов или для создания C-интерфейсов для обмена данными между разными генерируемыми компиляторами кодом.Я также использовал их в качестве внутренних сведений о реализации, когда я реализую таблицы перехода, стираю стили и т. Д., Когда я их производю и потребляю, и не подвергаю его внешнему воздействию для любого используемого кода клиента, а указатели функций делают все, что мне нужно ,

Обратите внимание, что вы можете написать обертки, которые превращают std::function<int(int)> в обратный вызов стиля int(void*,int), предполагая наличие надлежащей инфраструктуры управления жизненным циклом обратного вызова. Так как тест на дым для любой системы управления временем обратного вызова C-стиля, я бы удостоверился, что упаковка std::function работает достаточно хорошо.

0

std::function может привести VMT к коду в некоторых случаях, что оказывает определенное влияние на производительность.

Смежные вопросы