Должен ли я использовать std:: function или указатель функции в С++?

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

void (*callbackFunc)(int);

Или я должен использовать std:: function:

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

Ответ 1

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

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

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

Подумайте о третьем варианте: если вы собираетесь реализовать небольшую функцию, которая затем сообщает что-то через предоставленную функцию обратного вызова, рассмотрите параметр template, который может быть любым вызываемым объектом, т.е. указатель функции, функтор, лямбда, std::function,... Недостатком здесь является то, что ваша (внешняя) функция становится шаблоном и, следовательно, должна быть реализована в заголовке. С другой стороны, вы получаете преимущество в том, что вызов обратного вызова может быть встроен, так как клиентский код вашей (внешней) функции "видит" вызов обратного вызова будет иметь доступную точную информацию о типе.

Пример для версии с параметром шаблона (напишите & вместо && для pre-С++ 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-стиль "обратный вызов с аргументами", который возможен в С++ (и, кстати, сильно используется в WIN32 API), но его следует избегать, потому что у нас есть лучшие варианты на С++.

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

(3) Для pre-С++ 11 используйте boost::function

Ответ 2

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

Хорошо спроектированный обратный вызов типа 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), предполагая наличие надлежащей инфраструктуры управления временем обратного вызова. Так как smoke test для любой системы управления жизненным циклом обратного вызова C-стиля, я бы удостоверился, что упаковка std::function работает достаточно хорошо.

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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