Будет ли одна и та же лямбда создаваться несколько раз в C++?

void do_something(std::function<void(void)> callback){
    callback();
}

int main() {
    int counter = 0;
    while(counter < 10000000) {
        do_something([&](){  std::cout << "by reference:" << counter << endl;  });
        do_something([=](){  std::cout << "by value:" << counter << endl;  });
        ++counter;
    }
}

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

Я искал некоторые статьи, но я все еще не совсем уверен. Я считаю, что лямбда-выражения для захвата значения должны создаваться несколько раз, а для захвата ссылок, вероятно, нет.

Если лямбда, захваченная по ссылке, может быть повторно использована, произойдет ли копирование лямбды? Например, будут ли по-прежнему иметь значение следующие две функции?

void do_something2(std::function<void(void)> callback){
    callback();
}

void do_something2(std::function<void(void)> & callback){
    callback();
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это зависит от того, что вы имеете в виду, определение лямбда создается только один раз. Экземпляр объекта лямбды создается много раз. Попробуйте c++ Insights это может дать вам много интересной информации. Например, в вашем случае:

 while(counter < 5) {
            
      class __lambda_16_20
      {
        public: 
        inline /*constexpr */ void operator()() const
        {
          std::operator<<(std::cout, "by reference:").operator<<(counter).operator<<(std::endl);
        }
        
        private: 
        int & counter;
        public: 
        // inline /*constexpr */ __lambda_16_20(const __lambda_16_20 &) noexcept = default;
        // inline /*constexpr */ __lambda_16_20(__lambda_16_20 &&) noexcept = default;
        __lambda_16_20(int & _counter)
        : counter{_counter}
        {}
        
      };
      
      do_something(std::function<void ()>(__lambda_16_20{counter}));

Вы можете видеть, что определение лямбды генерируется внутри while, но только 1 раз. Экземпляр объекта (в данном случае __lambda_16_20 создается каждый раз, когда вы проходите через цикл.

В вашем примере с:

void do_something2(std::function<void(void)> & callback)

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

do_something2([=](){  std::cout << "by value:" << counter << endl;  });

Не компилируется. Вместо этого вам понадобится изменяемое значение:

auto my_lambda = [=](){  std::cout << "by value:" << counter << endl;  };
do_something(my_lambda{});

Делаете ли вы my_lambda в цикле или вне цикла, зависит от вас. Затем вам нужно будет дважды проверить, как вы используете захват по ссылке и захват по значению.

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

Wayne Tseng 21.02.2023 06:05

Кстати, если мы убедимся, что обратный вызов захватывает лямбда по ссылке, всегда используйте "void do_something(std::function<void(void)> & callback)" будет лучше, чем "void do_something(std::function<void( void)> обратный вызов)"?

Wayne Tseng 21.02.2023 06:06

Для Q1 это так, но какова «стоимость создания лямбды». Убедитесь, что вы избегаете преждевременной оптимизации, потому что вы можете сделать свой код более сложным для понимания, а результаты профилирования могут вас удивить. Непрерывная передача ссылки может быть медленнее, чем передача по значению, потому что ваш компилятор может просто оптимизировать все, что нужно сделать, чтобы это убрать.

Fantastic Mr Fox 21.02.2023 07:41

Что значит «лучше» для Q2? Читабельнее? Конечно нет. Быстрее? Может быть, а может и нет, вам нужно будет профилировать реальный пример.

Fantastic Mr Fox 21.02.2023 07:56

@FantasticMrFox Предлагаемое вами изменение неконстантного эталонного случая не решает его, потому что требуется ссылка на std::function<...>. В предложенном коде все еще есть преобразование из лямбды в std::function<...>, которое создает временный объект, который не может привязываться к неконстантной ссылке.

j6t 21.02.2023 09:06

@ j6t правда, но я не предлагал это решение. ОП спросил об этом случае. Мое предлагаемое решение состояло бы в том, чтобы использовать лямбда как есть, компилятор оптимизирует это, и это вряд ли будет источником каких-либо реальных узких мест.

Fantastic Mr Fox 21.02.2023 22:58

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