Когда использовать функторы над лямбдами
Есть ли когда-нибудь ситуация, когда имеет смысл создавать функтор, а не использовать лямбду?
Я знаю, что мой вопрос фактически противоположен тому, когда использовать лямбду над функтором, но я не могу вспомнить ситуацию на практике, когда функтор предпочтительнее лямбды. Есть мысли по этому поводу?
4 ответа
Лямбда - это функтор, который определен с более коротким синтаксисом.
Проблема в том, что этот синтаксис ограничен. Это не всегда позволяет вам решить проблему наиболее эффективным и гибким способом - или вообще. До C++14 operator()
не мог даже быть шаблоном.
Кроме того, лямбда имеет ровно один operator()
, Вы не можете предоставить несколько перегрузок, чтобы различать, скажем, типы аргументов:
struct MyComparator
{
bool operator()( int a, int b ) const {return a < b;}
bool operator()( float a, float b ) const {return /*Some maths here*/;}
};
.. или категория значения аргумента объекта (то есть вызываемого объекта замыкания). Вы также не можете определить специальные функции-члены, включая конструкторы и деструкторы. Что если функтор будет отвечать за ресурсы?
Другая проблема с лямбдами заключается в том, что они не могут быть рекурсивными. Конечно, нормальные функции (включая операторские функции) могут быть.
Также учтите, что лямбда-выражения неудобны для использования в качестве компараторов для ассоциативных контейнеров или средств удаления для интеллектуальных указателей: вы не можете напрямую передавать тип замыкания в качестве аргумента шаблона и должны создавать член контейнера из другого объекта замыкания. (Типы замыкания не имеют конструктора по умолчанию!). Для блочного охвата map
это не слишком много хлопот:
auto l = [val] (int a, int b) {return val*a < b;};
std::map<int, int, decltype(l)> map(l);
Теперь, что произойдет, если ваш map
такое член данных? Какой шаблонный аргумент, какой инициализатор в списке инициализации конструкторов? Вы должны будете использовать другой член статических данных - но так как вы должны определить его вне определения классов, которое, возможно, уродливо.
Подводя итог: лямбды бесполезны для более сложных сценариев, потому что они не были созданы для них. Они предоставляют краткий и краткий способ создания простых функциональных объектов для соответственно простых ситуаций.
Я хотел бы рассмотреть использование функтора над лямбда, когда я
- хочу более одного экземпляра.
- должны сделать расширенную обработку ресурсов.
- нужно ссылаться на функтор как на тип. Например, чтобы передать его в качестве параметра шаблона.
- (related) может дать значимое и в целом полезное имя для типа (в отличие от экземпляра).
- обнаружите, что логика может быть написана чище, если разделить на подфункции. В лямбде мы должны записать все в одну функцию.
Я мог бы подумать о двух случаях:
Когда функтор несет внутреннее состояние, таким образом, существует нетривиальная проблема времени жизни с функтором. Это держит "что-то" между использованиями
Когда вам нужно использовать один и тот же код повсеместно, с точки зрения обслуживания, хорошей идеей может быть написание и хранение его в качестве функтора в собственном заголовке.
Лямбда не может быть использована в неоцененном контексте. Особо надуманный пример украден из ответа Шафика:
Во многих случаях это просто бесполезно, поскольку каждая лямбда имеет уникальный тип, приведенный гипотетический пример:
template<typename T, typename U> void g(T, U, decltype([](T x, T y) { return x + y; }) func); g(1, 2, [](int x, int y) { return x + y; });
Тип лямбда- выражения в объявлении и вызове различен (по определению), и поэтому это может не сработать.
Таким образом, даже лямбда с одинаковым синтаксисом не может использоваться вместо другого. Функтор будет работать в этом случае.