Чем отличаются функции move и forward?

138
28 апреля 2019, 16:20

С функцией move вроде бы всё понятно: она приводит любую ссылку на объект к неконстантной rvalue-ссылке, которая в соответствующем контексте передастся либо конструктору перемещения, либо оператору присваивания перемещения. Но я иногда встречаю в коде на C++ вызов функции forward в таком же контексте, где я бы использовал move. То, что можно нагуглить про эту функцию, понять не получается. Что она делает?

Answer 1

Функция forward, как и функция move, выполняет приведение аргумента к rvalue. Отличие ее от move в том, что приведение она выполняет условное - в зависимости от аргумента результатом forward может быть как rvalue, так и lvalue. Используется forward в идиоматической конструкции прямой передачи (perfect forwarding), а выглядит эта идиома так:

template <typename T>
void f(T&& arg) {
  . . .
  g(std::forward<T>(arg));
}

Суть этой идиомы в том, что если ссылка на шаблонизированный тип имеет строго вид T&&, и если в процессе вывода типа компилятор выполняет свертывание ссылок, то в результате свертывания результирующая ссылка может быть выведена как rvalue, так и lvalue. Использовать move в таком случае нельзя, так как программист, вызывающий функцию f с обычной переменной в качестве аргумента, может не подозревать, что ее аргумент исчезнет. Тогда как forward в таком случае корректно передаст аргумент в g как lvalue.

В стандарте такая ссылка называется "передаваемая ссылка" (forwarding reference), но с подачи Скотта Мейерса используется и название "универсальная ссылка" (universal reference).

Шаблон может быть и неявным, например:

int j = 0;
auto&& r = j;  // Вывод типа происходит, так что r - передаваемая ссылка.

Ситуация с auto&& типична для лямбда функций, так что там тоже надо употреблять move с осторожностью.

Простой пример работы forward и move

std::string gstr;
template<typename T>
void g(T arg) {
  gstr = std::move(arg);
}
template<typename T>
void f(T&& arg) {
  g(std::move(arg));
}
int main() {
  std::string str { "test" };
  f(str);

После выполнения f(str) строка "test" перемещается в gstr, чего программист, вероятно, не ожидал. Но вот если вызов g записать как g(std::forward<T>(arg)), то функция forward распознает аргумент arg как lvalue и строка корректно скопируется.

Answer 2

forward нужен для того, чтобы пробрасывать изначальный тип дальше по цепочке. Отсюда его имя и единственное назначение. Вот пример:

template<typename... Args>
void secondLevel(Args&& ...args)
{
    ...
}
template<typename... Args>
void firstLevel(Args&& ...args)
{
    secondLevel(std::forward<Args>(args)...);
}

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

В этой статье я приводил ещё один пример.

READ ALSO
Удаление елементов из QListWidget

Удаление елементов из QListWidget

Доброго времени суток

128
Техника pimpl c++

Техника pimpl c++

Что такое техника pimpl и когда нужно её использовать?

146
Поиск значения в std::vector

Поиск значения в std::vector

Я создал следующую структуру

136
Удаление строк в QTableView

Удаление строк в QTableView

Столкнулся с одной проблемойУ меня есть таблица, в которой по нажатию кнопок можно добавлять, вставлять между существующих строк и удалять...

193