Использование std:: forward vs std:: move
Я всегда читаю, что std::forward
предназначен только для использования с параметрами шаблона. Однако я спрашивал себя, почему. См. Следующий пример:
void ImageView::setImage(const Image& image){
_image = image;
}
void ImageView::setImage(Image&& image){
_image = std::move(image);
}
Это две функции, которые в основном делают то же самое; один берет ссылку на l-значение, а другой - r-значение. Теперь я подумал, что, поскольку std::forward
должен возвращать ссылку l-value, если аргумент является ссылкой l-value и ссылкой r-value, если аргумент один, этот код можно упростить примерно так:
void ImageView::setImage(Image&& image){
_image = std::forward(image);
}
Какой тип похож на пример cplusplus.com упоминает для std::forward
(только без параметров шаблона). Я просто хотел бы знать, если это правильно или нет, и если не почему.
Я также спрашивал себя, что именно будет иметь значение
void ImageView::setImage(Image& image){
_image = std::forward(image);
}
Ответы
Ответ 1
Вы не можете использовать std::forward
без явного указания его аргумента шаблона. Он намеренно используется в невыводимом контексте.
Чтобы понять это, вам нужно действительно понять, как перенаправление ссылок (T&&
для выведенного T
) работает внутри, а не отмахивается от них как "это волшебство". Поэтому давайте посмотрим на это.
template <class T>
void foo(T &&t)
{
bar(std::forward<T>(t));
}
Скажем, мы называем foo
следующим образом:
foo(42);
42
- это значение типа int
. T
выводится на int
. Поэтому вызов bar
использует int
как аргумент шаблона для std::forward
. Тип возврата std::forward<U>
равен U &&
. В этом случае int &&
, поэтому T
пересылается как rvalue.
Теперь позвоните foo
следующим образом:
int i = 42;
foo(i);
i
- это значение типа int
. Из-за специального правила для идеальной пересылки, когда lvalue типа V
используется для вывода T
в параметре типа T &&
, для вычитания используется V &
. Поэтому в нашем случае T
выводится int &
.
Поэтому мы указываем int &
как аргумент шаблона std::forward
. Поэтому его возвращаемый тип будет "int & &&
", который падает до int &
. Это lvalue, поэтому i
отправляется как lvalue.
Резюме
Почему это работает с шаблонами, когда вы делаете std::forward<T>
, T
иногда является ссылкой (когда оригинал является lvalue), а иногда нет (когда оригинал является rvalue). std::forward
, следовательно, будет привязан к значению lvalue или rvalue, если это необходимо.
Вы не можете сделать эту работу в версии без шаблона именно потому, что у вас будет только один доступный тип. Не говоря уже о том, что setImage(Image&& image)
вообще не принимает значения lvalues; lvalue не может связываться с rvalue-ссылками.
Ответ 2
Я рекомендую прочитать "Эффективный современный С++", его автором является Scott Meyers.
Пункт 23: Поймите std:: move и std:: forward.
Пункт 24: Различать универсальные ссылки для ссылок rvalue.
С чисто технической точки зрения ответ да: std:: forward может сделать все это. std:: move не требуется. Конечно, ни одна функция действительно необходимо, потому что мы могли писать касты повсюду, но я надеюсь, мы согласны с тем, что это было бы, ну, yucky. std:: перемещает достопримечательности являются удобством, уменьшенной вероятностью ошибки и большей ясностью.
rvalue-reference: эта функция принимает значения rvalues, которые не могут принимать lvalues.
void ImageView::setImage(Image&& image){
_image = std::forward(image); //error
_image = std::move(image);//conventional
_image = std::forward<Image>(image);//unconventional
}
Обратите внимание, что std:: move требует только аргумент функции, в то время как std:: forward требует как аргумента функции, так и шаблона аргумент типа.
template <typename T> void ImageView::setImage(T&& image){
_image = std::forward<T>(image);
}
универсальные ссылки (ссылки на пересылку). Эта функция принимает все и делает отличную переадресацию.
Ответ 3
Вы должны указать тип шаблона в std::forward
.
В этом контексте Image&& image
всегда является ссылкой r-value, а std::forward<Image>
всегда перемещается, поэтому вы можете использовать std::move
.
Ваша функция, принимающая ссылку на r-значение, не может принимать значения l, поэтому она не эквивалентна первой двум функциям.