Подтвердить что ты не робот

Выражение Lambda vs. Functor в С++

Интересно, где мы должны использовать лямбда-выражение над функтором в С++. Для меня эти два метода в основном одинаковы, даже функтор более изящный и чище, чем лямбда. Например, если я хочу повторно использовать свой предикат, мне придется копировать лямбда-часть снова и снова. Итак, когда на самом деле появляется лямбда?

4b9b3361

Ответ 1

1) Это тривиально и пытается поделиться им - это больше работы, чем пользы.

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

Если ни одна из этих вещей не верна, возможно, вам стоит подумать об определении функтора.

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

typedef std::vector< std::pair<int,std::string> > whatsit_t;

int find_it(std::string value, whatsit_t const& stuff)
{
  auto fit = std::find_if(stuff.begin(), stuff.end(), [value](whatsit_t::value_type const& vt) -> bool { return vt.second == value; });

  if (fit == stuff.end()) throw std::wtf_error();

  return fit->first;
}

Без lambdas вам нужно будет использовать что-то, что аналогично построит функтор на месте или напишет объект, связанный с внешней связью, для чего-то, что раздражающе тривиально.

Кстати, я думаю, возможно, wtf_error является расширением.

Ответ 2

Лямбда-выражение создает неназванный функтор, это синтаксический сахар.

Поэтому вы в основном используете его, если он делает ваш код лучше. Это обычно происходит, если либо (а) вы не собираетесь повторно использовать функтор, либо (б) вы собираетесь его повторно использовать, а из кода, полностью не связанного с текущим кодом, который, чтобы поделиться им, в основном в конечном итоге создайте my_favourite_two_line_functors.h, и на нем будут разрозненные файлы.

Довольно же те же условия, при которых вы вводили бы любую строку (строки) кода, а не абстрагировали бы этот блок кода в функцию.

Тем не менее, с операторами range-for в С++ 0x есть некоторые места, где вы бы использовали функтор раньше, чем это могло бы сделать ваш код лучше выглядеть теперь, чтобы написать код как тело цикла, а не функтора или лямбда.

Ответ 3

Lambdas - это просто синтаксический сахар, который реализует функторы (NB: закрытие не просто.) В С++ 0x вы можете использовать ключевое слово auto для локального хранения lambdas, а функция std:: позволит вам хранить lambdas, или передавать их безопасным образом.

Ознакомьтесь с статьей Википедии о С++ 0x.

Ответ 4

Маленькие функции, которые не повторяются.

Основная жалоба на функторов заключается в том, что они не находятся в том же месте, где они использовались. Таким образом, вам нужно было найти и прочитать функтор вне контекста в том месте, где он использовался (даже если он используется только в одном месте).

Другая проблема заключалась в том, что функтор потребовал некоторую проводку для получения параметров в объекте-функторе. Не сложный, а весь базовый шаблонный код. И плита котла подвержена резанию и вставке.

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

Ответ 5

Лямбда и функтор имеют контекст. Функтор является классом и поэтому может быть более сложным, чем лямбда. Функция не имеет контекста.

#include <iostream>
#include <list>
#include <vector>
using namespace std;

//Functions have no context, mod is always 3
bool myFunc(int n) { return n % 3 == 0; }

//Functors have context, e.g. _v
//Functors can be more complex, e.g. additional addNum(...) method
class FunctorV
{
   public:
   FunctorV(int num ) : _v{num} {}

   void addNum(int num) { _v.push_back(num); }

   bool operator() (int num)
   {
      for(int i : _v) {
         if( num % i == 0)
            return true;
      }
      return false;
   }

   private:
   vector<int> _v;
};

void print(string prefix,list<int>& l)
{
   cout << prefix << "l={ ";
   for(int i : l)
      cout << i << " ";
   cout << "}" << endl;
}

int main()
{
   list<int> l={1,2,3,4,5,6,7,8,9};
   print("initial for each test: ",l);
   cout << endl;

   //function, so no context.
   l.remove_if(myFunc);
   print("function mod 3: ",l);
   cout << endl;

   //nameless lambda, context is x
   l={1,2,3,4,5,6,7,8,9};
   int x = 3;
   l.remove_if([x](int n){ return n % x == 0; });
   print("lambda mod x=3: ",l);
   x = 4;
   l.remove_if([x](int n){ return n % x == 0; });
   print("lambda mod x=4: ",l);
   cout << endl;

   //functor has context and can be more complex
   l={1,2,3,4,5,6,7,8,9};
   FunctorV myFunctor(3);
   myFunctor.addNum(4);
   l.remove_if(myFunctor);
   print("functor mod v={3,4}: ",l);
   return 0;
}

Выход:

initial for each test: l={ 1 2 3 4 5 6 7 8 9 }

function mod 3: l={ 1 2 4 5 7 8 }

lambda mod x=3: l={ 1 2 4 5 7 8 }
lambda mod x=4: l={ 1 2 5 7 }

functor mod v={3,4}: l={ 1 2 5 7 }

Ответ 6

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

Ответ 7

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

size_t length = strlen(x) + sizeof(y) + z++ + strlen('\0');
...
allocate(length);
std::cout << length;

... здесь, создавая переменную длины, поощряет программу считать ее правильностью и значением в изоляции от нее позже. Надеемся, что имя надежно передает достаточно, чтобы его можно было понять интуитивно и независимо от его начального значения. Затем он позволяет использовать значение несколько раз без повторения выражения (при обращении z к другому). Хотя здесь...

allocate(strlen(x) + sizeof(y) + z++ + strlen('\0'));

... общий код уменьшается, а значение локализуется в нужной точке. Единственное, что нужно "переносить вперед" из чтения этой строки, это побочные эффекты распределения и приращения (z), но нет дополнительной локальной переменной с объемом или более поздним использованием. Программист должен мысленно манипулировать меньшим состоянием, продолжая анализ кода.

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

Ответ 8

Во-первых, я хотел бы прояснить некоторые проблемы здесь.

Есть две разные вещи

  1. Лямбда-функция
  2. Лямбда-выражение/функтор.

Обычно лямбда-выражение, т.е. [] () {} -> return-type, не всегда синтезируется в замыкание (т.е. вид функтора). Хотя это зависит от компилятора. Но вы можете заставить компилятор принудительно установить знак + перед [] как +[] () {} -> return-type. Это создаст указатель на функцию.

Теперь перейдем к вашему вопросу. Вы можете использовать лямбда несколько раз следующим образом:

int main()
{
    auto print = [i=0] () mutable {return i++;};
    cout<<print()<<endl;
    cout<<print()<<endl;
    cout<<print()<<endl;
    // Call as many time as you want
    return 0;
}

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

Если вы комбинируете Lambda с другими функциями, такими как constexpr, variadic template parameter pack или generic lambda. Вы можете достичь многих вещей.

Вы можете найти больше об этом здесь