2015-03-03 3 views
34

Я всегда читал, что std::forward предназначен только для использования с параметрами шаблона. Однако я спрашивал себя, почему. См. Следующий пример:Использование std :: forward vs std :: move

void ImageView::setImage(const Image& image){ 
    _image = image; 
} 

void ImageView::setImage(Image&& image){ 
    _image = std::move(image); 
} 

Это две функции, которые в основном делают то же самое; один берет ссылку на l-значение, а другой - r-значение. Теперь я думал, что, поскольку std::forward должен возвращать ссылку на l-значение, если аргумент является ссылкой на l-значение и ссылкой на r-значение, если аргумент равен единице, этот код можно было бы упростить примерно так:

void ImageView::setImage(Image&& image){ 
    _image = std::forward(image); 
} 

Какой тип похож на пример cplusplus.com упоминает для std::forward (только без параметров шаблона). Я просто хотел бы знать, если это правильно или нет, и если не почему.

Я также спрашивал себя, что именно будет разница в

void ImageView::setImage(Image& image){ 
    _image = std::forward(image); 
} 
+7

Чтобы это понять, посмотрите это видео: http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Scott-Meyers-Universal-References-in-Cpp11 –

+2

Проблема: переслать _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ Фактически, он полностью игнорирует значение-категорию параметра. –

ответ

54

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

Чтобы понять это, вам необходимо понять, как пересылать ссылки (T&& для выведенного T), и не отмахивать их как «это волшебство». Итак, давайте посмотрим на это.

template <class T> 
void foo(T &&t) 
{ 
    bar(std::forward<T>(t)); 
} 

Скажем, мы называем foo так:

foo(42); 

42 является Rvalue типа int. T выведено на номер int. Таким образом, вызов bar использует int в качестве аргумента шаблона для std::forward. Возвращаемый тип std::forward<U> - U &&. В этом случае это int &&, поэтому t перенаправляется как rvalue.

Теперь, давайте назовем foo так:

int i = 42; 
foo(i); 

i является объект типа int. Из-за специального правила для идеальной пересылки, когда для вывода используется значение l 44, номер T &&, V & используется для вычитания. Поэтому в нашем случае T выведено как int &.

Поэтому мы указываем int & в качестве аргумента шаблона для std::forward. Поэтому его тип возврата будет «int & &&», который разрушается до int &. Это lvalue, поэтому i перенаправляется как lvalue.

Резюме

Почему это работает с шаблонами, когда вы делаете std::forward<T>, T иногда ссылка (если оригинал именующий), а иногда нет (когда оригинал является Rvalue). Поэтому std::forward будет относиться к значению lvalue или rvalue, если это необходимо.

Вы не можете сделать эту работу в версии без шаблона именно потому, что у вас будет только один доступный тип. Не говоря уже о том, что setImage(Image&& image) не принимал lvalues ​​вообще — lvalue не может связываться с rvalue ссылками.

+0

Хорошо, я думаю, что понял это до сих пор. Что мне было интересно: я могу вызвать функцию 'template void foo (T && t)' с ссылками на l-значение, а также с ссылками на r-значение? И в случае, если ссылка Lvalue T выводится как 'V &', это означает, что тип аргументов становится '(V & &&)', который является в основном ссылкой rvlaue на ссылку lvalue (или что?)? Итак, это точка, в которой поведение отличается от функций, отличных от шаблонов, где функция 'void foo (MyType && t)' может быть вызвана только с помощью ссылок на r-значение? – user1488118

+2

@ user1488118 Имеются правила сбрасывания ссылок. 'T && &&' сворачивается в 'T &&', все остальные ('T & &&', 'T && &' и 'T & &') сворачиваются в 'T &'. --- Да, вывод 'V &' - это то, что позволяет вам вызвать шаблон функции с как lvalues, так и rvalues ​​(не обязательно ссылки). Функция без шаблона, принимающая 'Image &&', может быть вызвана только с rvalues ​​типа 'Image'. – Angew

+0

Хорошо. Если я объявлял свою функцию как «шаблон void foo (T & t)», то для чего будет выводиться T для вызовов типа 'foo (42)' или 'int i = 42; Foo (я); '? – user1488118

7

Вы должны указать тип шаблона в std::forward.

В этом контексте Image&& image является всегда ссылка на г-значение и std::forward<Image> всегда будет двигаться, так что вы могли бы также использовать std::move.

Ваша функция, принимающая ссылку на r-значение, не может принимать значения l, поэтому она не эквивалентна первым двум функциям.

+0

'std :: forward' будет перемещаться, только если используется как' std :: forward ', или' std :: forward ', а не * всегда *, как вы говорите –

+0

@PiotrS. Вы правы, я упрощал. Надеюсь, это лучше. –

15

Рекомендую прочитать «Эффективный современный C++», его автором является Scott Meyers.

Пункт 23: Поймите std :: move and std :: forward.

Пункт 24: Различать универсальные ссылки для ссылок rvalue.

С чисто технической точки зрения, да: std :: forward может сделать все. std :: move не требуется. Конечно, ни одна из функций действительно не нужна, потому что мы могли писать касты повсюду, но я надеюсь, что мы согласны с тем, что это будет, ну, yucky. std :: достопримечательности: - удобство, уменьшенная вероятность ошибки и большей ясности.

Rvalue ссылка: Эта функция принимает rvalues ​​не может принять lvalues.

void ImageView::setImage(Image&& image){ 
    _image = std::forward(image); //error 
    _image = std::move(image);//conventional 
    _image = std::forward<Image>(image);//unconventional 

} 

Примечание первое, что станд :: движение требует только аргумент функции , в то время как станд :: вперед требует как аргумент функции и тип аргумента шаблона .

template <typename T> void ImageView::setImage(T&& image){ 
    _image = std::forward<T>(image); 
} 

универсальные ссылки (ссылки) переадресации: Эта функция принимает все и делает идеальную переадресацию.

+3

N.B. официальное название «универсальной ссылки» теперь _forwarding reference_ –

+0

@JonathanWakely - Спасибо, я вижу. –

+0

@JonathanWakely: _ «официальное название« универсальной ссылки »теперь пересылает ссылку« _ - я не получил записку. С каких пор? Где было объявлено? Благодарю. –

Смежные вопросы