С функцией move
вроде бы всё понятно: она приводит любую ссылку на объект к неконстантной rvalue-ссылке, которая в соответствующем контексте передастся либо конструктору перемещения, либо оператору присваивания перемещения. Но я иногда встречаю в коде на C++ вызов функции forward
в таком же контексте, где я бы использовал move
. То, что можно нагуглить про эту функцию, понять не получается. Что она делает?
Функция 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 и строка корректно скопируется.
forward
нужен для того, чтобы пробрасывать изначальный тип дальше по цепочке. Отсюда его имя и единственное назначение. Вот пример:
template<typename... Args>
void secondLevel(Args&& ...args)
{
...
}
template<typename... Args>
void firstLevel(Args&& ...args)
{
secondLevel(std::forward<Args>(args)...);
}
Используя пробравсывающие ссылки, мы получаем «истинный» тип переданного нам выражения, а с помощью forward
мы сохраняем этот же тип для функции secondLevel
неизменным.
В этой статье я приводил ещё один пример.
Виртуальный выделенный сервер (VDS) становится отличным выбором
Столкнулся с одной проблемойУ меня есть таблица, в которой по нажатию кнопок можно добавлять, вставлять между существующих строк и удалять...