Для иллюстративных целей скажем, я хочу реализовать универсальную функцию сравнения целого числа. Я могу придумать несколько подходов для определения/вызова функции.
(A) Шаблон функции + функторы
template <class Compare> void compare_int (int a, int b, const std::string& msg, Compare cmp_func)
{
if (cmp_func(a, b)) std::cout << "a is " << msg << " b" << std::endl;
else std::cout << "a is not " << msg << " b" << std::endl;
}
struct MyFunctor_LT {
bool operator() (int a, int b) {
return a<b;
}
};
И это будет пара вызовов этой функции:
MyFunctor_LT mflt;
MyFunctor_GT mfgt; //not necessary to show the implementation
compare_int (3, 5, "less than", mflt);
compare_int (3, 5, "greater than", mflt);
(B) Шаблон функции + lambdas
Мы будем называть compare_int
следующим образом:
compare_int (3, 5, "less than", [](int a, int b) {return a<b;});
compare_int (3, 5, "greater than", [](int a, int b) {return a>b;});
(C) Шаблон функции + std:: function
Та же реализация шаблона, вызов:
std::function<bool(int,int)> func_lt = [](int a, int b) {return a<b;}; //or a functor/function
std::function<bool(int,int)> func_gt = [](int a, int b) {return a>b;};
compare_int (3, 5, "less than", func_lt);
compare_int (3, 5, "greater than", func_gt);
(D) Указатели "C-style"
Реализация:
void compare_int (int a, int b, const std::string& msg, bool (*cmp_func) (int a, int b))
{
...
}
bool lt_func (int a, int b)
{
return a<b;
}
Призвание:
compare_int (10, 5, "less than", lt_func);
compare_int (10, 5, "greater than", gt_func);
В этих сценариях мы имеем в каждом случае:
(A) Два экземпляра шаблона (два разных параметра) будут скомпилированы и выделены в памяти.
(B) Я бы сказал, что два экземпляра шаблона будут скомпилированы. Каждая лямбда - это другой класс. Поправьте меня, если я ошибаюсь, пожалуйста.
(C) Скомпилирован только один экземпляр шаблона, поскольку параметр шаблона всегда те же: std::function<bool(int,int)>
.
(D) Очевидно, что у нас есть только один экземпляр.
Разумеется, это не имеет никакого значения для такого наивного примера. Но при работе с десятками (или сотнями) шаблонов и многочисленными функторами время компиляции и разность использования памяти могут быть существенными.
Можно ли сказать, что во многих случаях (т.е. при использовании слишком большого числа функторов с одной и той же сигнатурой) std::function
(или даже указатели на функции) должны быть предпочтительнее над шаблонами + исходными функторами /lambdas? Обертка вашего функтора или лямбда с помощью std::function
может быть очень удобной.
Я знаю, что std::function
(указатель функции тоже) вводит служебные данные. Стоит ли это?
EDIT. Я сделал очень простой тест, используя следующие макросы и очень распространенный шаблон стандартной библиотеки (std:: sort):
#define TEST(X) std::function<bool(int,int)> f##X = [] (int a, int b) {return (a^X)<(b+X);}; \
std::sort (v.begin(), v.end(), f##X);
#define TEST2(X) auto f##X = [] (int a, int b) {return (a^X)<(b^X);}; \
std::sort (v.begin(), v.end(), f##X);
#define TEST3(X) bool(*f##X)(int, int) = [] (int a, int b) {return (a^X)<(b^X);}; \
std::sort (v.begin(), v.end(), f##X);
Ниже приведены результаты относительно размера сгенерированных двоичных файлов (GCC при -O3):
- Binary с 1 экземпляром макроса TEST: 17009
- 1 экземпляр макроса TEST2: 9932
- 1 экземпляр макроса TEST3: 9820
- 50 экземпляров макросов TEST: 59918
- 50 экземпляров макроса TEST2: 94682
- 50 экземпляров макроса TEST3: 16857
Даже если я показал цифры, это более качественный, чем количественный ориентир. Как мы и ожидали, функциональные шаблоны, основанные на std::function
параметре или указателе функции, выглядят лучше (с точки зрения размера), так как создается не так много экземпляров. Однако я не измерял использование памяти во время выполнения.
Что касается результатов работы (размер вектора составляет 1000000 элементов):
- 50 экземпляров макросов TEST: 5.75s
- 50 экземпляров макроса TEST2: 1,54 с
- 50 экземпляров макроса TEST3: 3.20s
Это заметная разница, и мы не должны пренебрегать служебными данными, введенными std::function
(по крайней мере, если наши алгоритмы состоят из миллионов итераций).