Использование std :: move для перехода во временную лямбду или для «вытягивания» вне временного параметра и в чем разница?

У меня есть следующий (надуманный) код, где у меня есть класс принтера с единственной функцией печати и рабочим классом, который обрабатывает строку и затем вызывает функцию обратного вызова для функции печати:

#include <functional>
#include <iostream>

using callback_fn = std::function<bool(std::string)>;

class printer
{
public:
bool print(std::string data)
{
std::cout << data << std::endl;
return true;
}
};

class worker
{
public:
callback_fn m_callback;
void set_callback(callback_fn callback)
{
m_callback = std::move(callback);  // <-- 1. callback is a temp, so what does std::move do here?
}
void process_data(std::string data)
{
if (!m_callback(data)) { /* do error handling */ }
}
};

int main() {
printer p;
worker w;

w.set_callback( std::move([&](std::string s){ return p.print(s); }) ); // <-- 2. what does std::move do here?
w.process_data("hello world2");
}

Замечания: я имею std:: move() звонили дважды … теперь это работает (удивительно для меня), но у меня есть оба, только чтобы показать, что я пытаюсь. Мои вопросы:

  1. Должен ли я использовать std::move() в set_callback() функция, чтобы «вытащить» темп, и если я использую это действительно есть копия или делает std:: move() Значит, это не совсем копия?
  2. Должен ли я использовать std:: move() пройти в лямбду … и это даже правильно.
  3. Я думаю, я не понимаю, почему этот код работает с двумя std:: moves()… что означает, что я до сих пор не понимаю, что std:: move() делает — так что если кто-то может рассказать мне о том, что здесь происходит, это было бы здорово!
  4. Я знаю, что могу передавать по значению, но моя цель состоит в том, чтобы сдвинуть временную копию, чтобы у меня не было ее копии. Это то, что подразумевается под идеальная пересылка?

Мой пример можно увидеть здесь, в wandbox: https://wandbox.org/permlink/rJDudtg602Ybhnzi

ОБНОВИТЬ
Причина, по которой я пытался использовать std :: move, заключалась в том, чтобы не копировать лямбду. (Я думаю, что это называется пересылкой / совершенной пересылкой) … но я думаю, что я делаю из этого хеш!

9

Решение

Я думаю, я не понимаю, почему этот код работает с двумя std :: move …
что подразумевает, что я до сих пор не понимаю, что делает std :: move

std::move чтобы сделать это явным в тех случаях, когда вы собираетесь двигаться из объект. Переместить семантику предназначены для работы с rvalues. Таким образом, std::move() принимает любое выражение (например, lvalue) и создает Rvalue из этого. Такое использование обычно возникает, когда вам нужно разрешить именующий быть переданным перегрузкам функции, которые принимают Rvalue ссылка в качестве параметра, такого как переместить конструкторы а также операторы перемещения. Идея перемещения состоит в том, чтобы эффективно передавать ресурсы, а не делать копии.

В вашем фрагменте вы не используете std::move() недопустимым образом, следовательно этот код работает. В остальной части ответа мы пытаемся понять, является ли это использование выгодным или нет.

Должен ли я использовать std :: move для перехода в лямбду

Кажется, нет, у вас нет причин делать это во фрагменте. Прежде всего, ты звонишь move() на что безоговорочно уже Rvalue. Далее, синтаксически, set_callback() получает его std::function<bool(std::string)> аргумент по значению, из которого ваша лямбда инициализирует экземпляр просто отлично в настоящее время.

Должен ли я использовать std :: move в функции set_callback ()

Не ясно на 100%, что вы получаете, используя переехать версия оператор присваивания на m_callback переменная-член, вместо обычного присваивания. Это не приведет к неопределенному поведению, поскольку вы не пытаетесь использовать аргумент после его перемещения. Кроме того, так как C ++ 11 callback параметр в set_callback() будет двигаться построен за rvalues такой как ваш временный, и копия, созданная для именующий, например, если бы вы назвали это так:

auto func = [&](std::string s){ return p.print(s); };
w.set_callback(func);

Что нужно учитывать, так это то, лучше ли внутри метода перемещение, чем копирование в вашем случае. Переезд предполагает собственную реализацию переместить назначение для соответствующего типа. Я не просто говорю здесь QOI, но учту, что при переезде вам нужно освободить любой ресурс m_callback держался до этого момента, и для сценария перехода от созданного экземпляра (как мы уже говорили, callback была либо построена как копия, либо как построена на основе ее аргумента), это добавляет к стоимости, которую уже имела эта конструкция. Не уверен, что такие движущиеся издержки применимы в вашем случае, но тем не менее ваша лямбда не так уж и дорога для копирования, как есть. Тем не менее, выбирая две перегрузки, один принимает const callback_fn& callback и копирование присваивание внутри и один взяв callback_fn&& callback и назначение движения внутрь позволило бы смягчить эту потенциальную проблему в целом. Как и в любом из них, вы ничего не создаете для параметра, и в целом вы не обязательно высвобождаете старые ресурсы в качестве накладных расходов, как при выполнении копирования-назначения, вы можете потенциально использовать уже существующие ресурсы LHS, копируя на них вместо того, чтобы выпустить его до перемещения тех из RHS.

Я знаю, что могу передавать по значению, но моя цель состояла в том, чтобы
что у меня нет его копии (идеальная пересылка?)

В контексте вывода типа (template или же auto), T&& это экспедиционная ссылка, не Rvalue ссылка. Таким образом, вы должны написать функцию только один раз (шаблонная функция, без перегрузок) и полагаться на нее std::forward (эквивалентно static_cast<T&&>) удостоверится, что в любом случае использования вышеописанный путь для использования двух перегрузок сохраняется с точки зрения стоимости, являющейся назначением копии для именующий вызов и перемещение-назначение для Rvalue вызов:

template<class T>
void set_callback(T&& callback)
{
m_callback = std::forward<T>(callback);
}
4

Другие решения

В этой строке

w.set_callback( std::move([&](std::string s){ return p.print(s); }) );

вы бросаете значение в значение. Это неоперация и, следовательно, бессмысленно. Передача временного значения в функцию, которая принимает ее параметр по значению, подходит по умолчанию. Аргумент функции, скорее всего, будет создан в любом случае. В худшем случае, он построен на ходу, который не требует явного вызова std::move на аргумент функции — опять же, так как это уже rvalue в вашем примере. Чтобы прояснить ситуацию, рассмотрим этот другой сценарий:

std::function<bool(std::string)> lValueFct = [&](std::string s){ /* ... */ }

// Now it makes sense to cast the function to an rvalue (don't use it afterwards!)
w.set_callback(std::move(lValueFct));

Теперь для другого случая. В этом фрагменте

void set_callback(callback_fn callback)
{
m_callback = std::move(callback);
}

Вы двигаетесь-назначаете m_callback, Это нормально, так как параметр передается по значению и впоследствии не используется. Один хороший ресурс по этой технике — это пункт 41 в Eff. Современный C ++. Здесь Мейерс также указывает, однако, что, хотя в целом нормально использовать конструкцию pass-by-value-затем-move-для инициализация, это не обязательно лучший вариант для назначение, потому что параметр по значению должен выделять внутреннюю память для хранения нового состояния, в то время как он может использовать существующий буфер при прямом копировании из constпараметр эталонной функции. Это является примером для std::string аргументы, и я не уверен, как это можно передать std::function экземпляры, но поскольку они стирают базовый тип, я мог представить, что это проблема, особенно для больших замыканий.

4

В соответствии с C ++ ссылка std::move это просто приведение к значению.

  1. Вам не нужно использовать std::move внутри вашего set_callback метод. std::function это CopyConstructible и CopyAssignable (документы), так что вы можете просто написать m_callback = callback;, Если вы используете std::function Переместите конструктор, объект, из которого вы переместились, будет «неопределенным» (но все еще действительным), в частности, он может быть пустым (что не имеет значения, потому что это временный объект). Вы также можете прочитать Эта тема.
  2. То же самое и здесь. Лямбда-выражение является временным и истекает после вызова set_callback, поэтому не имеет значения, переместите ли вы его или скопируете.
  3. Код работает, потому что ваши обратные вызовы построены (двигаться построены) правильно. Нет причин для того, чтобы код не работал.

Ниже приведен пример с ситуацией, когда std::move имеет значение:

callback_fn f1 = [](std::string s) {std::cout << s << std::endl; return true; };
callback_fn f2 = f1;
f1("xxx");  // OK, invokes previously assigned lambda
f2("xxx");  // OK, invokes the same as f1
f2 = std::move(f1);
f1("xxx");  // exception, f1 is unspecified after move

Выход:

xxx
xxx
C++ exception with description "bad_function_call" thrown in the test body.
2
По вопросам рекламы [email protected]