Когда использовать функторы над лямбдами

Есть ли когда-нибудь ситуация, когда имеет смысл создавать функтор, а не использовать лямбду?

Я знаю, что мой вопрос фактически противоположен тому, когда использовать лямбду над функтором, но я не могу вспомнить ситуацию на практике, когда функтор предпочтительнее лямбды. Есть мысли по этому поводу?

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) может дать значимое и в целом полезное имя для типа (в отличие от экземпляра).
  • обнаружите, что логика может быть написана чище, если разделить на подфункции. В лямбде мы должны записать все в одну функцию.

Я мог бы подумать о двух случаях:

  1. Когда функтор несет внутреннее состояние, таким образом, существует нетривиальная проблема времени жизни с функтором. Это держит "что-то" между использованиями

  2. Когда вам нужно использовать один и тот же код повсеместно, с точки зрения обслуживания, хорошей идеей может быть написание и хранение его в качестве функтора в собственном заголовке.

Лямбда не может быть использована в неоцененном контексте. Особо надуманный пример украден из ответа Шафика:

  1. Во многих случаях это просто бесполезно, поскольку каждая лямбда имеет уникальный тип, приведенный гипотетический пример:

    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; });
    

    Тип лямбда- выражения в объявлении и вызове различен (по определению), и поэтому это может не сработать.

Таким образом, даже лямбда с одинаковым синтаксисом не может использоваться вместо другого. Функтор будет работать в этом случае.

Другие вопросы по тегам