В чем разница между std :: move и std :: forward

160

Я видел это здесь: Move Constructor вызывает базовый класс Move Constructor

Может ли кто-нибудь объяснить:

  1. разница между std::moveи std::forward, желательно с некоторыми примерами кода?
  2. Как легко об этом думать, и когда использовать какой
aCuria
источник
1
Смотрите также эти два связанных вопроса: Должен ли я использовать std :: move или std :: forward в операторах перемещения / операторах присваивания? и как работает std :: forward? (а также дублированный вопрос).
Xeo
«Как легко об этом думать и когда использовать, какой» Вы используете, moveкогда хотите переместить значение и forwardкогда вы хотите использовать идеальную пересылку. Это не ракетостроение здесь;)
Никол Болас
move () выполняет безусловное приведение, где as forward () выполняет приведение на основе переданного параметра.
RaGa__M

Ответы:

159

std::moveберет объект и позволяет рассматривать его как временное (значение). Хотя это и не семантическое требование, обычно функция, принимающая ссылку на rvalue, делает ее недействительной. Когда вы видите std::move, это означает, что значение объекта не должно использоваться впоследствии, но вы все равно можете назначить новое значение и продолжить его использование.

std::forwardимеет один вариант использования: для приведения параметра функции-шаблона (внутри функции) к категории значений (lvalue или rvalue) вызывающая сторона использовала его для передачи. Это позволяет передавать аргументы rvalue как значения rvalue, а lvalues ​​- как lvalues ​​- схему, называемую «идеальная пересылка».

Для иллюстрации :

void overloaded( int const &arg ) { std::cout << "by lvalue\n"; }
void overloaded( int && arg ) { std::cout << "by rvalue\n"; }

template< typename t >
/* "t &&" with "t" being template param is special, and  adjusts "t" to be
   (for example) "int &" or non-ref "int" so std::forward knows what to do. */
void forwarding( t && arg ) {
    std::cout << "via std::forward: ";
    overloaded( std::forward< t >( arg ) );
    std::cout << "via std::move: ";
    overloaded( std::move( arg ) ); // conceptually this would invalidate arg
    std::cout << "by simple passing: ";
    overloaded( arg );
}

int main() {
    std::cout << "initial caller passes rvalue:\n";
    forwarding( 5 );
    std::cout << "initial caller passes lvalue:\n";
    int x = 5;
    forwarding( x );
}

Как упоминает Говард, есть также сходства, поскольку обе эти функции просто приводятся к ссылочному типу. Но помимо этих конкретных случаев использования (которые охватывают 99,9% полезности приведенных ссылок на rvalue), вы должны использовать static_castнапрямую и написать хорошее объяснение того, что вы делаете.

Potatoswatter
источник
Я не уверен , что это точно , что std::forward«S только случай использования является совершенным экспедиторская аргументов функции. Я сталкивался с ситуациями, когда я хочу полностью пересылать другие вещи, такие как члены объекта.
Джефф Ромер
@GeoffRomer Члены параметра? У вас есть пример?
Potatoswatter
Я разместил пример в виде отдельного вопроса: stackoverflow.com/questions/20616958/…
Джефф Ромер
Хм, так что, если я правильно понимаю, это означает, что я могу написать одну функцию forward (), где forward (5) работает с 5, как если бы я передал его по значению, а forward (x) работает с x, как если бы я передал ссылка без необходимости явной записи перегрузки.
iheanyi
@iheanyi Да, это идея! Но это должен быть шаблон, а не просто обычная функция.
Potatoswatter
63

Оба std::forwardи std::moveничто иное как слепки.

X x;
std::move(x);

Выше приведено выражение lvalue xтипа X к выражению rvalue типа X (точнее, xvalue). moveтакже можно принять значение:

std::move(make_X());

и в этом случае это функция тождества: принимает r-значение типа X и возвращает r-значение типа X.

С помощью std::forwardвы можете выбрать пункт назначения в некоторой степени:

X x;
std::forward<Y>(x);

Приводит выражение lvalue xтипа X к выражению типа Y. Существуют ограничения на то, что может быть Y.

Y может быть доступной Базой X, или ссылкой на Базу X. Y может быть X, или ссылкой на X. Нельзя отбрасывать cv-квалификаторы с помощью forward , но можно добавлять cv-квалификаторы. Y не может быть типом, который просто конвертируется из X, кроме как через доступное базовое преобразование.

Если Y является ссылкой lvalue, результатом будет выражение lvalue. Если Y не является ссылкой lvalue, результатом будет выражение rvalue (точнее xvalue).

forwardможет принимать аргумент rvalue, только если Y не является ссылкой lvalue. То есть вы не можете привести значение к значению lvalue. Это по соображениям безопасности, так как это обычно приводит к висящим ссылкам. Но приведение rvalue к rvalue - это нормально и разрешено.

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

Говард Хиннант
источник
Если я полностью перенаправлю объект в функцию с помощью std::forward, то после выполнения этой функции смогу ли я использовать этот объект? Я знаю, что, в случае std::move, это неопределенное поведение.
iammilind
1
Относительно move: stackoverflow.com/a/7028318/576911 Поскольку forward, если вы передаете lvalue, ваш API должен реагировать так, как будто он получает lvalue. Обычно это означает, что значение будет неизменным. Но если это неконстантное значение, ваш API, возможно, изменил его. Если вы передаете значение r, это обычно означает, что ваш API, возможно, переместился с него, и, таким образом, будет применяться stackoverflow.com/a/7028318/576911 .
Говард
21

std::forwardиспользуется для пересылки параметра , точно так , как он был передан в функцию. Как показано здесь:

Когда использовать std :: forward для пересылки аргументов?

Использование std::moveпредлагает объект в качестве rvalue, чтобы, возможно, соответствовать конструктору перемещения или функции, принимающей rvalue. Это делает это, std::move(x)даже если xсамо по себе не является значением.

Бо Перссон
источник