2009-12-18 2 views
43

У меня вопрос о том, какой стиль является предпочтительным: std :: bind Vs lambda в C++ 0x. Я знаю, что они служат - как-то - разные цели, но позволяют взять пример пересекающихся функций.Bind Vs Lambda?

Использование lambda:

uniform_int<> distribution(1, 6); 
mt19937 engine; 
// lambda style 
auto dice = [&]() { return distribution(engine); }; 

Использование bind:

uniform_int<> distribution(1, 6); 
mt19937 engine; 
// bind style 
auto dice = bind(distribution, engine); 

Какой мы должны предпочесть? Зачем? предполагая более сложные ситуации по сравнению с указанным примером. Т.е. какие преимущества/недостатки одного над другим?

+1

Есть ли отличия в производительности? скорость, потребление памяти, использование кучи? –

+0

@Caspin Я не знаю, есть ли разница в производительности/потреблении памяти между этими двумя инструментами :) – AraK

+4

Кстати: две версии не эквивалентны, потому что привязывают копии аргументов. Альтернатива: bind (ref (распространение), ref (двигатель)) – sellibitze

ответ

22

Как вы сказали, bind и лямбды не совсем точно нацелены на одну и ту же цель.

Например, для использования и составления алгоритмов STL лямбды являются явными победителями, ИМХО.

Чтобы проиллюстрировать, я помню действительно смешной ответ здесь, когда переполнение стека, где кто-то просил идеи шестизначных магических чисел (например, 0xDEADBEEF, 0xCAFEBABE, 0xDEADDEAD и т. Д.), И ему сказали, что если бы он был настоящим программистом на C++ он бы просто загрузить список английских слов и использовать простой однострочник C++ :)

#include <iterator> 
#include <string> 
#include <algorithm> 
#include <iostream> 
#include <fstream> 
#include <boost/lambda/lambda.hpp> 
#include <boost/lambda/bind.hpp> 

int main() 
{ 
    using namespace boost::lambda; 
    std::ifstream ifs("wordsEn.txt"); 
    std::remove_copy_if(
     std::istream_iterator<std::string>(ifs), 
     std::istream_iterator<std::string>(), 
     std::ostream_iterator<std::string>(std::cout, "\n"), 
     bind(&std::string::size, _1) != 8u 
      || 
     bind(
      static_cast<std::string::size_type (std::string::*)(const char*, std::string::size_type) const>(
       &std::string::find_first_not_of 
      ), 
      _1, 
      "abcdef", 
      0u 
     ) != std::string::npos 
    ); 
} 

Этого фрагмента кода, в чистом C++ 98, открыть файл английских слов, сканировать каждое слово и печать только те длиной 8 с буквами 'a', 'b', 'c', 'd', 'e' или 'f'.

Теперь включите C++ 0x и лямбда:

#include <iterator> 
#include <string> 
#include <algorithm> 
#include <iostream> 
#include <fstream> 

int main() 
{ 
std::ifstream ifs("wordsEn.txt"); 
std::copy_if(
    std::istream_iterator<std::string>(ifs), 
    std::istream_iterator<std::string>(), 
    std::ostream_iterator<std::string>(std::cout, "\n"), 
    [](const std::string& s) 
    { 
     return (s.size() == 8 && 
       s.find_first_not_of("abcdef") == std::string::npos); 
    } 
); 
} 

Это еще немного тяжело читать (в основном из-за istream_iterator бизнеса), но намного проще, чем версия привязки :)

+0

Хотя две части кода не делают то же самое, я очень четко понял вашу точку зрения :) – AraK

+4

лямбда должна быть: [] (const std :: string & s) -> bool – 2009-12-19 11:00:53

+9

@Beh Tou Cheh I подумайте, что тип должен быть выведен, если лямбда состоит из 'return ;' only (как Томас сделал). – AraK

17

Синтаксис C++ 0x lamdba более читабельен, чем синтаксис связывания. После того, как вы переходите к связыванию более 2-3 уровней, код становится практически нечитаемым и его трудно поддерживать. Я бы предпочел более интуитивный синтаксис лямбда.

+0

Не согласен. '[this]() {Type * this_too = this; run ([this_too]() {this_too-> f();});} 'не является ни читабельным, ни интуитивным. –

+5

Полагаю, что новые линии помогут вашему контрпримеру. Новые строки не сильно повлияли бы на bind. –

3

Я думаю, что это скорее вопрос вкуса. Люди, которые быстро овладевают новыми технологиями или знакомы с функциональным программированием, вероятно, предпочтут лямбда-синтаксис, в то время как более консервативные программисты окончательно предпочтут связывание, так как он больше соответствует традиционному синтаксису C++.

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

Что не меняет факт, однако, что синтаксис лямбда гораздо более мощный и чище.

+3

Люди в команде продолжают меняться. Чтение кода очень важно, особенно. для будущих программистов по обслуживанию. Следовательно, мы должны пойти с любым решением, которое предлагает большую читаемость и между lamdba и bind, lamda определенно берет торт. – posharma

8

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

С привязкой вы вынуждены создавать новую функцию/метод/функтор, даже если логика нужна только в этом месте. Вам нужно придумать соответствующее имя, и это может сделать код менее понятным, поскольку он потенциально может привести к раздельной логике.

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

+1

+1. Мне пришлось закрыть вектор FILE * в dtor. Вместо того, чтобы использовать lambda '[] (FILE * f) {if (f) fclose (f); } 'Мне пришлось создать именованную функцию и использовать ее. Функция появилась в 'private' части класса и, таким образом, была разделена многими строками из вызова' for_each' – KitsuneYMG

0

C++ 0x lambdas существенно заменить bind. Нет ничего, что вы могли бы связать, чтобы вы не могли воссоздать тривиальный оберточный лямбда для достижения того же. std :: tr1 :: bind будет идти по пути std :: bind1st и т. д., когда поддержка lambda широко распространена. Это хорошо, потому что по какой-то причине большинству программистов приходится сталкиваться с трудностями.

+3

Хотя этот ответ был неверен в момент его публикации, он верен для C++ 14. Ссылка в комментарии выше подтверждает это сейчас. –

+0

@gnzlbg См. Приведенный выше комментарий. –

39

C++ 0x lambdas являются мономорфными, а связывание может быть полиморфным. У вас не может быть чего-то вроде

auto f = [](auto a, auto b) { cout << a << ' ' << b; } 
f("test", 1.2f); 

a и b должны иметь известные типы. С другой стороны, TR1/импульс/феникс/лямбда привязки позволяет это сделать:

struct foo 
{ 
    typedef void result_type; 

    template < typename A, typename B > 
    void operator()(A a, B b) 
    { 
    cout << a << ' ' << b; 
    } 
}; 

auto f = bind(foo(), _1, _2); 
f("test", 1.2f); // will print "test 1.2" 

Обратите внимание, что типы А и В не фиксирован. Только когда f фактически используется, эти два будут выведены.

+0

Почему бы просто не объявить лямбду с явно типизированными параметрами? Это было бы большим улучшением над решением связывания, показанным выше. Кроме того, если у вас есть более сложные функции, которые вы хотите повторно использовать, лямбда по-прежнему лучше, чем привязка, поскольку для нее не требуется структура, даже если вы хотите связать состояние с функтором: 'template <...> foo (A a, B b, int state) {cout ... << state; } ... auto f = [] (const char * a, float b) {foo (a, b, 42); }; '. –

+2

@Marcelo Cantos: утверждение, подтверждающее, что «C++ 0x lambdas мономорфны», именно потому, что вы _must_ объявляете лямбду с явно типизированными параметрами. – MSalters

+0

@MSalters: Вопрос был (примерно): «Что лучше?» Я не уверен, как доказать, что C++ 0x lambdas являются мономорфными ответами на вопрос. –