LINUX.ORG.RU

std::bind vs lambda

 , ,


1

5

Есть метод, который принимает callback:

void Bar::setCallback(const std::function<void(int, int)> &cb) {}

Я туда хочу передать метод класса, соответственно есть два пути:

// Метод, который передаём
void Foo::callback(int a, int b) {}

// Путь первый
bar->setCallback(std::bind(&Foo::callback, this, std::placeholders::_1, std::placeholders::_2));

// Путь второй
bar->setCallback([this] (int a, int b) { callback(a, b); });

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

★★★★★

Последнее исправление: ox55ff (всего исправлений: 1)

лямбды во всём лучше.

fsb4000 ★★★★★
()
// Путь второй

Это читабельней.

thunar ★★★★★
()

лямбды как ни странно выглядят лучше, бинд понятнее но слишком многословно.

anonymous
()
Ответ на: удаленный комментарий

Сделать using namespace std::placeholders, конечно, религия не позволяет?

anonymous
()

Синтаксис bind конечно же читабельнее, удобнее и не подвержен ошибкам, т.к. не надо указывать все аргументы по два раза плюс их типы плюс пачку скобок. std::placeholders писать долго, поэтому можно так:

using namespace std::placeholders;
bar->setCallback(std::bind(&Foo::callback, this, _1, _2}); 

а в последнем стандарте вообще так:

bar->setCallback(std::bind_front(&Foo::callback, this)); 

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

slovazap ★★★★★
()
Ответ на: комментарий от anonymous

Позволяет. Делаю. Здесь не стал писать.

ox55ff ★★★★★
() автор топика
Ответ на: комментарий от anonymous

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

anonymous
()

std::mem_fn? А не, не так прочитал. nvm.

unC0Rr ★★★★★
()
Последнее исправление: unC0Rr (всего исправлений: 1)

Я за лямбды - они интуитивно понятнее. Где-то Мэйерс вроде писал, что с появлением лямбд необходимость в std::bind пропала.

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)
Ответ на: комментарий от anonymous

using namespace - это моветон

Нет. Он имеет область видимости. Можно сузить до тела метода, где он используется. Странно, что такой ЭКСПЕРТ по крестам как ты об этом не знает. + никто не предлагает его писать в заголовочных файлах.

сами они решить уже не могут

При этом в тексте поста:

Ваше личное мнение.

Ты жопой читаешь, анон? Я спрашиваю мнение других людей.

Короче, ты вошёл и обосрался. Подайте следующего анончика.

ox55ff ★★★★★
() автор топика
Ответ на: комментарий от anonymous

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

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)
Ответ на: комментарий от anonymous

а то что using namespace - это моветон, вам конечно в школе не рассказывали

Нет конечно, потому что это ложь. Вы должно быть из слышали звон, но до конца не дослушали, а главное головой не подумали почему моветон и для кого моветон, и моветон ли. Претензии выдвигались только и исключительно к using на верхнем уровне в header’ах. Это да, поскольку неконтролируемо засоряет область видимости. А в любых других местах это современный механизм контроля видимости который надо использовать, чтобы не писать, например std::placeholder несколько раз в строке.

slovazap ★★★★★
()

третий путь, напиши свой велосипедный «функциональный объект» :) Но не, бери лучше лямбды.

slackwarrior ★★★★★
()

Лямбды быстрее, понятнее, можно захватывать внешние объекты, не загаживают пространство имен, занимают меньше места. О чем тут вообще можно думать?

anonymous
()
Ответ на: комментарий от anonymous

bind понятнее, внешние объекты здесь захватывать не надо, пространство имён bind не загаживает, это же не макра.

slovazap ★★★★★
()

std::bind понятнее, на плюсах не пишу.

paramon
()
Ответ на: комментарий от slovazap

Настолько понятнее, что топикстартер скобку позабыл. Еще небось и хэдер надо подключать какой-нибудь. А эти убогие placeholder’ы чего стоят. Короче, легаси, затыкающее отсутствие лямбд стандарте 20-летней давности.

anonymous
()

и то и другое выглядит как булшит.

vtVitus ★★★★★
()

Второй вариант. std::bind воспринимается как аппендицит, перекочевавший из Буста в C++11.

mkam
()

без разницы, используй любой. посколько в интерфейсе торчит std::function, то лямбда всё равно будет преобразована к ней, так же как и std::bind, что тебе больше нравится, в любом случае ты получишь функтор который вызывается в рантайме.

чтобы заюзать лямбды на 100% тебе надо переделать интерфейс так:

void Bar::setCallback(auto cb) { cb(); }

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

я лично думаю, что лямбда всё равно читаемее. std::bind - это когда тебе надо создать руками (частичное) замыкание, что лямбда тоже может сделать. оно бывает полезно когда тебе надо одну функцию преобразовать в другую функцию, чего ты тут не делаешь, поэтому std::bind тебе наверное тоже не нужен, так что фигачь лучше лямбды.

anonymous
()
Ответ на: комментарий от anonymous

Есть метод, который принимает callback:

void Bar::setCallback(const std::function<void(int, int)> &cb) {}

сорри... я тут мимокрокодил, я бы это переделал на:

template<typename Function, typename Class, typename Tuple>
auto Bar::applyCallback(Function f, Class c, Tuple t)

+ std::apply внутри... либо если не с++17 и еще распаковка через std::index_sequence, вам точно пары int хватает?

anonymous2 ★★★★★
()
Последнее исправление: anonymous2 (всего исправлений: 2)

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

А почему больше нравится вариант с биндом - потому что он более базовый так сказать и понятен даже тем кто плюсы не знает.

bonta ★★★★★
()
bar->setCallback([this] (auto&& ... ts) { callback(ts...); });

а вообще, если есть возможность переделать интерфейс, лучше сделать тип Bar обобщенным с типом callback’а в качестве параметра.

Siborgium ★★★★★
()

Код с std::bind однозначно сложнее и при написании, и при чтении, потому что там используется больше библиотечного кода и надо подключать лишние хедеры (<functional>). Я всегда напишу лямбду в таком коде.

std::function лучше не использовать вообще никогда. Эта хрень под капотом может аллоцировать память и вызывает виртуальные методы, не давая компилятору оптимизировать код. От неё фактически отказались даже в STL, передавая функции просто параметром шаблона. Типа такого.

template< class RandomIt, class Compare >
void sort( RandomIt first, RandomIt last, Compare comp );
Lrrr ★★★★★
()
Ответ на: комментарий от Lrrr

std::function лучше не использовать вообще никогда.

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

ox55ff ★★★★★
() автор топика
Ответ на: комментарий от Siborgium

Ты не написал std::forward, тебя заклюют

anonymous
()
Ответ на: комментарий от anonymous

если ты лямбду куда-то присваиваешь

Да, я сохраняю в поле класса. Потом вызываю, если понадобится.

ox55ff ★★★★★
() автор топика
Ответ на: комментарий от ox55ff

Что-нибудь типа такого.

class TaskBase {
  using Callback = void (*)(TaskBase* self, bool execute);

 public:
  void Complete() { callback_(this, true); }
  void Destroy() { callback_(this, false); }

 protected:
  TaskBase(Callback callback) : callback_{callback} {}

 private:
  Callback callback_;
};

template <typename Handler>
class Task : public TaskBase {
 public:
  template <typename Fn>
  Task(Fn&& fn) : TaskBase(DoCall), fn_{std::forward<Fn>(fn)} {}

 private:
  static void DoCall(TaskBase* base, bool execute) {
    auto* self = static_cast<Task*>(base);
    /* impl */
  }

 private:
  Handler fn_;
};

TaskBase можно еще сделать членом интрузивного списка.
А вообще можно boost.asio посмотреть по данному поводу. Не могу сходу найти нужное место в исходниках.

mkam
()
Последнее исправление: mkam (всего исправлений: 1)
Ответ на: комментарий от Lrrr

std::function лучше не использовать вообще никогда.

Прям какой-то незамутненный подростковый максимализм.

eao197 ★★★★★
()
Ответ на: комментарий от mkam

Чем это лучше тупого виртуального метода в TaskBase?

Ну и еще вам в TaskBase, скорее всего, потребуется виртуальный деструктор.

eao197 ★★★★★
()
Ответ на: комментарий от Lrrr

В терминах растишки std::function - это трейт-объект Box, где возможный «small size optimisation» - деталь реализации. Поэтому думать дальше и отличать трейт-объекты от замыканий!

anonymous
()
Ответ на: комментарий от mkam

о чем и речь: плюсарь для простого коллбэка зачем-то запилил целый темплейт и целый класс и еще наверное гордится этим. объясняй чем твоя портянка-велосипед лучше лямбды. объясняй зачем ты ее написал. извинисяйся за свое поведение.

anonymous
()
Ответ на: комментарий от eao197

Чем это лучше тупого виртуального метода в TaskBase?

Тем, что в очередь можно отправлять лямбды и std::function. Будет что-то типа

class TaskQueue {
 public:
  template <typename Handler>
  bool Post(Handler&& fn, bool force_task = false) {
    using FnType = std::decay_t<Handler>;

    auto task = std::make_unique<Task<FnType>>(std::forward<Handler>(fn));
    const auto result = Enqueue(*task, force_task);
    if (result) {
      task.release();
    }

    return result;
  }

  void ProcessOneTask(bool wait);
  void ProcessAllTasks(bool wait);

 private:
  bool Enqueue(TaskBase& task, bool force_task);
}

В TaskQueue же имплементируется очередь, позволяющая выполнять задачи в любом/любых тредах.

И использование
void foo() {
  int value = 0;

  queue.Post([&] { value += 1; });
  queue.Post([&] { value += 2; });
  queue.Post([&] { value += 3; });

  queue.ProcessAllTasks();
  ASSERT_EQ(value, 6);
}


Ну и еще вам в TaskBase, скорее всего, потребуется виртуальный деструктор.

Возможно. Зависит от имплементации. Собственно идея и растёт из boost.asio.
Можно добавить деструктор, можно поприседать как в boost.asio, а можно и использовать boost.asio и не забивать голову:)

https://github.com/boostorg/asio/blob/develop/include/boost/asio/detail/sched...
https://github.com/boostorg/asio/blob/develop/include/boost/asio/detail/execu...
https://github.com/boostorg/asio/blob/develop/include/boost/asio/detail/impl/...

mkam
()
Ответ на: комментарий от eao197

Чем это лучше тупого виртуального метода в TaskBase?

Возможно я неправильно понял твою идею с использованием виртуального метода.

mkam
()
Ответ на: комментарий от mkam

Возможно я неправильно понял твою идею с использованием виртуального метода.

Очевидно, что так.

В TaskBase можно иметь просто виртуальный метод invoke:

class TaskBase {
protected:
  TaskBase() = default;

public:
  virtual ~TaskBase() = default;

  virtual void invoke() = 0;
};

А в наследниках просто переопределять этот invoke:

template<typename Handler>
class Task : public TaskBase {
  Handler fn_;
public:
  template<typename Fn>
  Task(Fn && fn) : fn_{std::forward<Fn>(fn)} {}

  void invoke() override { fn_(); }
};

Зачем все эти приседания с указателем на функцию, дополнительные статические методы со static_cast внутри?

Ну и еще вам в TaskBase, скорее всего, потребуется виртуальный деструктор.

Возможно. Зависит от имплементации.

Зависит от имплементации очереди тасков. Т.к. если вы будете хранить unique_ptr<TaskBase>, то проблемы из-за отсутствия виртуального деструктора практически гарантированы.

Если же будет использоваться shared_ptr<TaskBase>, то подобных проблем можно избежать. Но это зависит от кривизны рук программиста.

Так что лучше сделать виртуальный деструктор. А отказаться от него только если профайлер покажет, что есть просадка производительности из-за этого виртуального вызова (что вряд ли будет иметь место в 99% случаев).

eao197 ★★★★★
()
Последнее исправление: eao197 (всего исправлений: 1)
Ответ на: комментарий от eao197

Очевидно, что так.

Теперь понял. Такой вариант мне больше нравится.

Так что лучше сделать виртуальный деструктор. А отказаться от него только если профайлер покажет, что есть просадка производительности из-за этого виртуального вызова (что вряд ли будет иметь место в 99% случаев).

С этим утверждением тоже согласен.

mkam
()
Ответ на: комментарий от rumgot

они понятные пока ты их используешь активно, давай хоть себе не врать, да и вообще лямбды это признак того, что нет порядка, типичный код-затычка. Вместо того чтобы написать аккуратных функций и сложить их в аккуратный файлик для этих функций и дополнять его по мере необходимости, ввели это «сейчас сбацаю прямо тут, пажи!», сейчас набегут апологеты лямбд и начнут рассказывать что это удобно, почти также удобно как объявление переменных перед использованием, но нет мои маленькие любители наговнякать по месту, это также удобно как и неудобно, на это можно смотреть с разных сторон. Именованные еще туда-сюда, но нахер они тогда вообще нужны, анонимные захардкоженные по месту использования - «ненужно».

anonymous
()
Ответ на: комментарий от ox55ff

тогда не парься, через что бы ты ни сделал, в конце концов ты свё равно получишь std::function. смотри что тебе и другим читабельнее, результат будет одинаковый.

anonymous
()
Ответ на: комментарий от anonymous

Так не нужно писать длинные функции в виде лямбд. Но вот например аргументы для всяких transform/generate/find_if и т.п. удобнее именно в виде лямбд, т.к. нафиг такой примитив оформлять в виде отдельных полноценных функций.

rumgot ★★★★★
()
Последнее исправление: rumgot (всего исправлений: 1)

передай туда класс и вызови что надо внутри.

deep-purple ★★★★★
()

Я далеко не эксперт в сях и даже вообще его не знаю, но дебажу код хрома два месяца, там все в bind.

gobot ★★★★
()
Ответ на: комментарий от Pavval

Не сказал бы что из-за этого. Там логика дебажилась, а на каком она языке не так важно. Хотя в чем то ты прав, если бы я лучше знал си, то не два месяца дебажил, а полтора. Больше проблем было со сборкой, там исходников на 30 гигабайт

gobot ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.