У меня есть следующий (надуманный) код, где у меня есть класс принтера с единственной функцией печати и рабочим классом, который обрабатывает строку и затем вызывает функцию обратного вызова для функции печати:
#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()
звонили дважды … теперь это работает (удивительно для меня), но у меня есть оба, только чтобы показать, что я пытаюсь. Мои вопросы:
std::move()
в set_callback()
функция, чтобы «вытащить» темп, и если я использую это действительно есть копия или делает std:: move(
) Значит, это не совсем копия?std:: move()
пройти в лямбду … и это даже правильно.std:: moves()
… что означает, что я до сих пор не понимаю, что std:: move()
делает — так что если кто-то может рассказать мне о том, что здесь происходит, это было бы здорово!Мой пример можно увидеть здесь, в wandbox: https://wandbox.org/permlink/rJDudtg602Ybhnzi
ОБНОВИТЬ
Причина, по которой я пытался использовать std :: move, заключалась в том, чтобы не копировать лямбду. (Я думаю, что это называется пересылкой / совершенной пересылкой) … но я думаю, что я делаю из этого хеш!
Я думаю, я не понимаю, почему этот код работает с двумя 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);
}
В этой строке
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
экземпляры, но поскольку они стирают базовый тип, я мог представить, что это проблема, особенно для больших замыканий.
В соответствии с C ++ ссылка std::move
это просто приведение к значению.
std::move
внутри вашего set_callback
метод. std::function
это CopyConstructible и CopyAssignable (документы), так что вы можете просто написать m_callback = callback;
, Если вы используете std::function
Переместите конструктор, объект, из которого вы переместились, будет «неопределенным» (но все еще действительным), в частности, он может быть пустым (что не имеет значения, потому что это временный объект). Вы также можете прочитать Эта тема.set_callback
, поэтому не имеет значения, переместите ли вы его или скопируете.Ниже приведен пример с ситуацией, когда 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.