С. Мейерс - Эффективный и современный C++ (1114942), страница 41
Текст из файла (страница 41)
С чисто техническойточки зрения ответ утвердительный: s t d : : forward может сделать все. Необходимостив std : : move нет. Конечно, ни одна из этих функций не является действительно необход имой, потому что мы могли бы просто вручную написать требуемое приведение, но, я надеюсь, мы сойдемся во мнении, что это будет как минимум некрасиво.Привлекательными сторонами s t d : : move являются удобство, снижение вероятности ошибок и большая ясность. Рассмотрим класс, в котором мы хотели бы отслеживатьколичество вызовов перемещающего конструктора.
Все, что нам надо, - это счетчик,объявленный как stat i c, который увеличивался бы при каждом вызове перемещающего конструктора. Полагая, что единственными нестатическими данными класса являетсяstd : : string, вот как выглядит обычный (т.е. использующий std : : move ) способ реализации перемещающего конструктора:-class Widget {puЬl i c :Widget (Widget & & rhs )s ( std: : шove ( rhs . s ) ){ ++rnoveCtorCa l l s ; 1private :static std: : s i ze t moveCtorCa l l s ;s t d : : st ring s ;1;Чтобы реализовать то же поведение с помощью s t d : : forward, код должен был бы выглядеть следующим образом:class Widget {puЫic :Widget ( Widget&& rhs )/ / Безусловная,s ( std: : forward<std : : string> ( rhs .
s ) ) / / нежела тельная++rnoveCtorCal l s ; 111 реализация};Заметим сначала, что s t d : : move требует только аргумент функции ( rhs . s ) , в товремя как s t d : : forward требует как аргумент функции ( rhs . s ) , так и аргумент типа1 70Гnава 5. Rvalue-ccыnки, семантика перемещений и прямая передачашаблона ( std : : string ) . Затем обратим внимание на то, что тип, который мы передаемs t d : : forward, должен быть не ссылочным, поскольку таково соглашение по кодированию, что передаваемый аргумент является rvalue (см. раздел 5.6). Вместе это означает,что std : : move требует меньшего ввода текста по сравнению с s t d : : forward и избавляетот проблем передачи типа аргумента, указывающего, что передаваемый аргумент является rvalue. s t d : : move устраняет также возможность передачи неверного типа (например,std: : string&, что привело бы к тому, что член-данные s был бы создан с помощью копирования, а не перемещения).Что еще более важно, так это то, что использование s t d : : move выполняет безусловное приведение к rvalue, в то время как использование s t d : : forward означает приведение к rvalue только ссылок, связанных с rvalue.
Это два совершенно различных действия.Первое из них обычно настраивает перемещение, в то время как второе просто передаетобъект другой функции способом, сохраняющим исходную характеристику объекта (lvalueили rvalue). Поскольку эти действия совершенно различны, наличие двух разных функций(и разных имен функций) является преимуществом, позволяющим их различать.Следует запомн ить•std : : move выполняет безусловное приведение к rvalue. Сама по себе эта функция неперемещает ничего.•std : : forward приводит свой аргумент к rvalue только тогда, когда этот аргументсвязан с rvalue.•Ни std : : move, ни s t d : : forward не выполняют никаких действий времени выполнения.5 .2.
Отnичие универса n ьны х ссыnок от rvalue-ccыno кГоворят, что истина делает нас свободными, но при соответствующих обстоятельстваххорошо выбранная ложь может оказаться столь же освобождающей. Этот раздел и естьтакой ложью. Но поскольку мы имеем дело с программным обеспечением, давайте избегать слова "ложь': а вместо него говорить, что данный раздел содержит "абстракцию".Чтобы объявить rvalue-ccылкy на некоторый тип Т, вы пишете Т & &. Таким образом,представляется разумным предположить, что если вы видите в исходном тексте Т & & , тоимеете дело с rvаluе-ссылками. Увы, не все так просто.void f (Widget&& pa ram) ;1 1 rvalue - ccылкaWidget&& varl1 1 rvalue - ccылкaauto&& var2=Widget ( ) ;varl ;1 1 Не rvalue - ccылкatemplate<typename Т>void f ( std: : vector<T>&& param) ; // rvalue - ccылкa5.2.Отличие универсальных ссылок от rvalue-cc ыл o к1 71ternplate<typenarne Т>void f ( T&& pararn) ;1 1 Не rvalue - ccылкaНа самом деле "т & & " имеет два разных значения.
Одно из них - конечно, rvа\uе-ссылка.Такие ссылки ведут себя именно так, как вы ожидаете: они связываются только с rvalueи их смь1сл заключается в идентификации объектов, которые могут быть перемещены.Другое значение "Т& & " ли бо rvа\uе-ссылка, ли бо \vа\uе-ссылка. Такие ссылки выглядят в исходном тексте как rvа\uе-ссылки (т.е. "т& &"), но могут вести себя так, как еслибы они были \vа\uе-ссылками (т.е.
"т&"). Такая дуальная природа позволяет им быть связанными как с rvalue (подобно rvа\uе-ссылкам), так и с lvalue (подобно \vа\uе-ссылкам).Кроме того, они могут быть связаны как с константными, так и с неконстантными объектами, как с объектами volat i l e, так и с объектами, не являющимися volat i le, и дажес объектами, одновременно являющимися и const, и volat i le.
Они могут быть связаныпрактически со всем. Такие беспрецедентно гибкие ссылки заслуживают собственногоимени, и я называю их универсальными ссылками 1 •Универсальные ссылки возникают в двух контекстах. Наиболее распространеннымявляются параметры шаблона функции, такие как в приведенном выше примере кода:-ternplate<typenarne Т>void f ( Т&& pararn) ;// pararn - универсальная ссылкаВторым контекстом являются объявления auto, включая объявление из приведенноговыше примера кода:auto&& var2 ; var l ;/ / var2 - универсальная ссылкаОбщее в этих контекстах - наличие вывода типа. В шаблоне f выводится тип pararn,а в объявлении var2 выводится тип переменной var2. Сравните это с приведенными далеепримерами (также взятыми из приведенного выше примера кода), в которых вывод типаотсутствует. Если вы видите "т & & " без вывода типа, то вы смотрите на rvа\uе-ссылку://11Widge t && varl ; Widget ( ) ; / /11void f (Widget&& pararn) ;Вывод типа отсутствует;pararn - rvalue-ccылкaВывод типа отсутствует;varl - rvalue-ccьшкaПоскольку универсальные ссылки являются ссылками, они должны быть инициализированы.
Инициализатор универсальной ссылки определяет, какую ссылку она представляет: \vа\uе-ссылку или rvа\uе-ссылку. Если инициализатор представляет собой rvalue, универсальная ссылка соответствует rvа\uе-ссылке. Если же инициализатор является lvalue,универсальная ссылка соответствует \vа\uе-ссылке. Для универсальных ссылок, которыеявляются параметрами функций, инициализатор предоставляется в месте вызова:ternplate<typenarne Т>void f (T&& pararn) ; // pararn является универсальной ссьшкой1 В разделе 5.3 поясняется, что к универсальным ссылкам почти всегда может применятьсяstd : : forward, так что, когда эта книга готовилась к печати, некоторые члены сообщества С++начинали именовать универсапьные ссыпки передаваемыми ссылками.1 72Глава 5. Rvаluе-ссылки, семантика леремещений и прямая передачаWidget w;f (W) ;/ / В f передается lvalue; тип param // Widge t& ( т .
е . lvalue - ccылкa )f ( std: : move (w) ) ;/ / В f передается rvalue; тип param 11 Widge t & & ( т . е . rvalue-ccыпкa )Чтобы ссылка была универсальной, вывод типа необходим, но не достаточен. Видобъявления ссылки также должен быть корректным, и этот вид достаточно ограничен.Он должен в точности имеет вид "т & & ': Взглянем еще раз на пример, который мы ужерассматривали ранее:template<typename Т>void f ( std: : vector<T>&& param ) ; / / param - rvalue - ccылкaКогда вызывается f, тип т выводится (если только вызывающий код явно его не укажет,но этот крайний случай мы не рассматриваем). Однако объявление типа param не имеет вида"т& &"; оно представляет собой std : : vector<T>& &.
Это исключает возможность для paramбыть универсальной ссылкой. Следовательно, param является rvаluе-ссылкой, что ваши компиляторы с удовольствием подтвердят при попытке передать в функцию f lvalue:std : : vector< int> v;f (V) ;11 Ошибка ! Невозможно связать lvalue11 с rvаluе - ссылкойДаже простого наличия квалификатора coпst достаточно для того, чтобы отобратьу ссылки звание универсальной:template<typename Т>void f ( const Т&& param ) ; // param - rvalue - ccылкaЕсли вы находитесь в шаблоне и видите параметр функции с типом "т& &': вы можетерешить, что перед вами универсальная ссылка. Но вы не должны этого делать, поскольку размещение в шаблоне не гарантирует наличие вывода типа.