Есть ли у bind() какие-либо преимущества (кроме совместимости) по сравнению с лямбда-выражениями С++ 11?

Я думаю о том, чтобы перенести свой код на использование лямбда-выражений в стиле С++ 11 вместо того, чтобы везде использовать bind. Но я не уверен, хорошая это идея или нет.

Использует ли, например. boost::lambda (или boost::phoenix) имеют какое-либо практическое преимущество перед лямбда-выражениями в стиле С++ 11?

Стоит ли переходить на лямбды? Должен ли я перенести свой код или нет?


person user541686    schedule 21.08.2012    source источник
comment
Иногда для повышения лямбда-функции писать гораздо меньше, а иногда вам не нужно повторять типы...   -  person PlasmaHH    schedule 22.08.2012
comment
@PlasmaHH: Да. Читаемость, совместимость и полиморфизм — это преимущества, упоминаемые во многих местах на SO, когда люди спрашивают о преимуществах лямбда-выражений (например, здесь) или попробуйте сравнить лямбды и bind. Но я разместил этот вопрос (и ответ), чтобы указать, что даже если вы находитесь в ситуации, когда они как читаемы, так и пригодны для использования (т.е. даже если вы не нужен полиморфизм, и даже если удобочитаемость не является проблемой), все же есть причины выбрать bind.   -  person user541686    schedule 22.08.2012
comment
Я не знаю о лямбдах boost. Но в контексте C++11 я услышал из презентации Скотта Мейерса: Lambdas обычно предпочтительнее, и они обычно генерируют лучший код. Вызовы через bind включают указатели на функции ⇒ без встраивания. Вызовы через замыкания допускают полное встраивание.   -  person bruziuz    schedule 06.06.2015


Ответы (3)


Основным преимуществом будут полиморфные функторы. В настоящее время лямбда-выражения C++11 являются мономорфными, т. е. они принимают только один тип аргумента, тогда как bind() позволяет создавать функторы, которые принимают любой тип аргумента, если связанный функтор может вызываться с ним.

#include <functional>

struct X{
  template<class T, class U>
  void operator()(T, U) const{}
};

int main(){
  X x;
  auto l_with_5 = [x](int v){ return x(v, 5); };
  auto b_with_5 = std::bind(x, std::placeholders::_1, 5);
  l(4);
  b("hi"); // can't do that with C++11 lambdas
}
person Xeo    schedule 21.08.2012
comment
Ха-ха, я задавал вопрос, исходя из предположения, что было бы так же просто использовать лямбда-выражения С++ 11 или boost:: lambda в первую очередь, но тем не менее это отличный момент, +1. - person user541686; 22.08.2012
comment
Я думаю, что полиморфность привязки заслуживает того, чтобы ее приняли, но проблема в том, что она уже упоминалась в миллионе других мест на StackOverflow, например здесь. С другой стороны, я не видел проблемы с размером кода, упомянутой в другом месте на SO, поэтому, поскольку люди все равно узнают об этом, я думаю, что мог бы вместо этого принять свой собственный ответ, хотя бы для того, чтобы донести это до людей. тоже внимание, хотя в целом это лучший ответ... - person user541686; 22.08.2012

Да, лямбда-выражения Boost полиморфны, лямбда-выражения C++11 — нет. Это означает, что, например, вы не можете сделать это с лямбда-выражениями C++11:

template<class T>
void f(T g)
{
    int x = 123;
    const char* y = "hello";
    g(x); // call with an integer
    g(y); // call with a string
}

int main() {
    f(std::cout << _1);
}
person Yakov Galka    schedule 21.08.2012
comment
Также см. это; это относится и к вашему ответу. - person user541686; 22.08.2012

Да: это может (иногда) существенно влиять на выходные размеры.

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

На первый взгляд это не кажется чем-то большим, пока вы не заметите:
Когда вы используете их внутри шаблонных функций, таких как std::sort, компилятор генерирует новый код для каждого другая лямбда.

Это может непропорционально увеличить размер кода.

bind, однако, обычно более устойчив к таким изменениям (хотя и не застрахован от них).

Чтобы проиллюстрировать, что я имею в виду...

  1. Возьмите приведенный ниже пример, скомпилируйте его с помощью GCC (или Visual C++) и обратите внимание на размер выходного двоичного файла.
  2. Попробуйте изменить if (false) на if (true) и посмотрите, как изменился размер выходного двоичного файла.
  3. Повторите #1 и #2 после того, как закомментируете все, кроме одного из stable_sort в каждой части.

Обратите внимание, что в первый раз лямбда-выражения C++11 немного меньше; после этого их размер увеличивается после каждого использования (около 3,3 КБ кода для каждой сортировки с VC++, аналогично GCC), тогда как двоичные файлы на основе boost::lambda практически не меняют свой размер (для меня он остается прежним, когда включены все четыре с точностью до полукилобайта).

#include <algorithm>
#include <string>
#include <vector>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/lambda.hpp>   // can also use boost::phoenix

using namespace boost::lambda;

struct Foo { std::string w, x, y, z; };

int main()
{
    std::vector<Foo> v1;
    std::vector<size_t> v2;
    for (size_t j = 0; j < 5; j++) { v1.push_back(Foo()); }
    for (size_t j = 0; j < v1.size(); j++) { v2.push_back(j); }
    if (true)
    {
        std::stable_sort(v2.begin(), v2.end(), bind(&Foo::w, var(v1)[_1]) < bind(&Foo::w, var(v1)[_2]));
        std::stable_sort(v2.begin(), v2.end(), bind(&Foo::x, var(v1)[_1]) < bind(&Foo::x, var(v1)[_2]));
        std::stable_sort(v2.begin(), v2.end(), bind(&Foo::y, var(v1)[_1]) < bind(&Foo::y, var(v1)[_2]));
        std::stable_sort(v2.begin(), v2.end(), bind(&Foo::z, var(v1)[_1]) < bind(&Foo::z, var(v1)[_2]));
    }
    else
    {
        std::stable_sort(v2.begin(), v2.end(), [&](size_t i, size_t j) { return v1[i].w < v1[j].w; });
        std::stable_sort(v2.begin(), v2.end(), [&](size_t i, size_t j) { return v1[i].x < v1[j].x; });
        std::stable_sort(v2.begin(), v2.end(), [&](size_t i, size_t j) { return v1[i].y < v1[j].y; });
        std::stable_sort(v2.begin(), v2.end(), [&](size_t i, size_t j) { return v1[i].z < v1[j].z; });
    }
}

Обратите внимание, что это обмен размера на скорость; если вы находитесь в очень очень тесном цикле, он может включать дополнительную переменную (поскольку теперь он использует указатели на члены).
Однако это ничего не имеет ничего общего с накладными расходами std::function (которые являются виртуальным вызовом), и даже это во многих случаях не поддается измерению, так что это не должно вызывать беспокойства.

person user541686    schedule 21.08.2012
comment
Если ваши лямбда-выражения каким-либо образом отличаются друг от друга Тип лямбда-выражения является уникальным типом, каждое лямбда-выражение отличается от всех других лямбда-выражений (где каждое лямбда-выражение означает лямбду, определенную в конкретной единице перевода в конкретной строке кода) - person David Rodríguez - dribeas; 22.08.2012
comment
@DavidRodríguez-dribeas: сначала я так и подумал, но некоторые люди указали, что компоновщик может объединять идентичный код, если это только разница в типах. Проверяя это прямо сейчас, кажется, что это относится к VC++, по крайней мере, частично (хотя я не знаю, работает ли он полностью); Я не знаю о GCC. - person user541686; 22.08.2012
comment
Быстрый тест с VS 2010, похоже, показывает, что это не так (возможно, это связано с каким-то флагом компилятора, или, может быть, я неправильно интерпретирую дамп символов --dumpbin /symbols--. - person David Rodríguez - dribeas; 22.08.2012
comment
@DavidRodríguez-dribeas: Вы компилируете одну единицу перевода или несколько? Я говорю об использовании в одной и той же единице перевода, и я смотрю на увеличение размера файла, а не на символы. Возможно, их объединяет компилятор, а не компоновщик. Кажется, что при дублировании лямбда-выражений размеры файлов увеличиваются больше, чем в идеале, но все же меньше, чем при разных лямбда-выражениях. - person user541686; 22.08.2012
comment
Мой собственный тест. int main() { std::vector<std::function<void(int)>> v; v.push_back([](int i){std::cout<<i<<"\n";}); /*repeated 5 times*/ for (auto it=v.begin();it!=v.end();++it){(*it)(5);}} Все в одной единице перевода. С 5 лямбдами: 79360 байт в .exe, с 10 лямбдами: 105472 - person David Rodríguez - dribeas; 22.08.2012
comment
Я не могу скомпилировать код вашего примера, используя GCC 4.7/clang 3.1 и boost 1.49. Компилятор не находит соответствующий оператор‹ для bind(...) ‹ bind(...). - person mfontanini; 22.08.2012
comment
@mfontanini: std::bind может быть спутано с boost::lambda::bind. Попробуйте указать их явно. - person user541686; 22.08.2012
comment
@DavidRodríguez-dribeas: я не уверен; разница не настолько велика, чтобы я мог сказать, что ее вызывает. Однако, если вы возьмете мой пример и вместо вызова stable_sort для полей x, y и z вы просто вызовете stable_sort для w четыре раза, вы должны увидеть, что размер файла уменьшится с 26,5 КБ до 16,5 КБ (или аналогичное уменьшение) . Это довольно важный признак того, что компилятор или компоновщик что-то объединяет (но какие именно части? Я не знаю). - person user541686; 22.08.2012
comment
Вот оно. Вероятно, ADL что-то напутал. C++11 Lambdas -> 35k. Материал Boost.Lambda -> 25k. - person mfontanini; 22.08.2012
comment
@mfontanini: Стоит отметить, что boost::phoenix немного делает его больше для меня, но, вероятно, не стоит об этом беспокоиться. - person user541686; 22.08.2012
comment
@Mehrdad: Учитывая, что вся программа состоит из 20 строк и единственная разница заключается в количестве лямбд, и что каждая лямбда представляет собой маленький объект (один вызов std::ostream::operator<<(int), разница в размерах заметна. - person David Rodríguez - dribeas; 22.08.2012
comment
@DavidRodríguez-dribeas: Страшно, не правда ли? :) - person user541686; 22.08.2012
comment
@Mehrdad: Не особенно страшно, просто факт: каждая лямбда определяет уникальный тип, и поэтому для него будет сгенерирован такой код. Даже если бы тест с VS показал обратное (т. е. даже если бы VS смог объединить все эквивалентные лямбда-выражения в одно), это зависело бы от компилятора. Язык довольно ясен: разные определения лямбда генерируют уникальные лямбда-типы, что, в свою очередь, означает, что если вы используете его в качестве аргументов для других шаблонов, которые будут генерировать еще больше кода. Кроме того, std::function не требует виртуальной диспетчеризации, а эффективность может быть аналогична эффективности вызова дополнительной функции. - person David Rodríguez - dribeas; 22.08.2012
comment
@DavidRodríguez-dribeas: Мой тест VS показал, что повторяющиеся лямбда-выражения генерируют меньше кода... Я не знаю, почему ваш не показал этого. Но да, это, очевидно, зависит от реализации... размер кода всегда зависит от реализации. В любом случае, как std::function не требует виртуальной диспетчеризации? Как вы думаете, как это вызывает лямбды? - person user541686; 22.08.2012
comment
@Mehrdad: Вот что я подумал, когда впервые услышал это. Выполнение стирания типов путем создания иерархии, в которой база имеет виртуальную exec (выберите свое имя для виртуальной функции здесь), является одним вариантом, но не единственным. Другой вариант — создать вместо базового шаблона шаблон function. Этот шаблон принимает функтор в качестве аргумента и выполняет над ним operator(), добавляя один дополнительный вызов невиртуальной функции. Затем объект std::function может хранить указатель на функцию (в этом описании отсутствуют некоторые детали, но вы можете их заполнить). - person David Rodríguez - dribeas; 22.08.2012
comment
давайте продолжим обсуждение в чате - person David Rodríguez - dribeas; 22.08.2012