Это зависит от вашего конкретного случая использования и от того, как пользователи добавляют свой код.Ниже приведен чисто теоретический анализ, но вы должны построить реалистичный сценарий и проверить производительность, прежде чем принимать решение.
Если код должен быть добавлен во время компиляции (т.е. вы предоставляете код пользователям, онисоздайте их логику и скомпилируйте все вместе) тогда, вероятно, лучший подход - предоставить шаблон, который принимает аргумент типа Functor
.
template <typename Functor> void doProcessing( Functor f) {
f( data );
}
Преимущество заключается в том, что компилятор имеет доступ ко всему коду, иэто означает, что он может принимать лучшие решения относительно того, содержит ли он код или нет, и может быть в состоянии дополнительно оптимизировать в случае встраивания.Недостатком является то, что вам нужно перекомпилировать программу с каждым новым фрагментом логики и что вам нужно скомпилировать ваш продукт вместе с пользовательскими расширениями, во многих случаях это невозможно.
Если необходимо выполнить расширения после компиляции вашего основного продукта, то есть клиенты могут создавать свои собственные расширения и использовать их в скомпилированном исполняемом файле (например, плагинах), тогда вы должны рассмотреть альтернативы:
принять указатель на функцию (способ C)
предоставить интерфейс (базовый класс) только с этой операцией (способ Java)
использоватьоболочка функтора (boost::function
/ std::function
), которая выполняет стирание типа
Они упорядочены по чистому исполнению.Стоимость пути C наименьшая из трех, но в большинстве случаев разница с опцией интерфейса незначительна (дополнительное косвенное обращение к вызову функции), и она дает вам возможность сохранить состояние в вызывающем объекте (это будет иметьдолжно быть выполнено через глобальное состояние в первом варианте).
Третий вариант является наиболее общим, поскольку он применяется стирание типа к пользователю вызываемый , и будетразрешить пользователям повторно использовать свой существующий код, используя функциональные адаптеры, такие как boost::bind
или std::bind
, а также лямбды, если их поддерживает компилятор.Это наиболее общий вариант и оставляет большинство вариантов выбора для пользователя.Однако с этим связана определенная стоимость: в стирании типа есть виртуальная отправка плюс дополнительный вызов функции для фактического фрагмента кода.
Обратите внимание, что если пользователю придется написать функцию для адаптации вашегоВ сочетании с их кодом стоимость этого созданного вручную адаптера, скорее всего, будет эквивалентна, поэтому, если они нуждаются в универсальности, тогда boost::function
/ std::function
- путь.
С учетом разницы вЗатраты на альтернативы, скорее всего, в целом очень малы, и вопрос о том, будет ли царапать одну или две операции, будет зависеть от того, насколько замкнут цикл и насколько дорог пользовательский код.Если код пользователя займет пару сотен инструкций, вероятно, нет смысла не использовать самое общее решение.Если цикл запускается несколько тысяч раз в секунду, выцарапывание нескольких инструкций также ничего не изменит.Вернитесь назад, напишите реалистичный сценарий и проведите тестирование, и во время тестирования будьте в курсе того, что действительно важно для пользователя.Царапание нескольких секунд операции, которая занимает минуты, не стоит терять гибкости решений более высокого уровня.