Подтвердить что ты не робот

Использование 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);
}
4b9b3361

Ответ 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, поэтому она не эквивалентна первой двум функциям.