С. Мейерс - Эффективный и современный C++ (1114942), страница 42
Текст из файла (страница 42)
Рассмотрим следующуюфункцию-член push_back в std : : vector:template<class Т,al locator<T>> / / Из стандарта С++class Allocatorclass vector {puЫ i c :void push_back ( T&& х ) ;};Параметр push_back, определенно, имеет верный для универсальной ссылки вид, нов данном случае вывода типа нет.
Дело в том, что push_back не может существовать безконкретного инстанцированного вектора, частью которого он является; а тип этого инстанцирования полностью определяет объявление push_back. Другими словами, кодstd : : vector<Widget> v;5.2.Отличие универсальных ссылок от rvalue-cc ыл oк1 73приводит к следующему инстанцированию шаблона std : : ve ctor:class vector<Widget , allocator<Widget>> {puЫ i c :/ / rvalue - ccьmкavoid push_back (Widget&& х ) ;};Теперь вы можете ясно видеть, что push_bac k не использует вывода типа.
Эта функцияpush_back для vector<T> (их две - данная функция перегружена) всегда объявляет параметр типа "rvalue-ccылкa на т ".В противоположность этому концептуально подобная функция-член ernplace_backв std : : vector выполняет вывод типа:template<class Т ,class Al locatorallocator<T>> / / / / Из стандарта С++cla s s vectorpuЬl i c :t emplate <class .
. . Args>void emplace_back (Args&& . . . a rgs ) ;};Здесь параметр типа Args не зависит от параметра типа вектора Т, так что Args должен выводиться при каждом вызове ernpl ace_back. (Да, в действительности Args представляет собой набор параметров, а не параметр типа, но для нашего рассмотрения егоможно рассматривать как параметр типа.)Тот факт, что параметр типа ernplace back имеет имя Args и является при этом универсальным указателем, только усиливает мое замечание о том, что универсальная ссылка обязана иметь вид "Т& &': Вы не обязаны использовать в качестве имени т.
Например,приведенный далее шаблон принимает универсальную ссылку, поскольку она имеет правильный вид ( " t yp e & &" ) , а тип pararn выводится (опять же, исключая крайний случай,когда вызывающий код явно указывает тип):_template<typename MyTemplateType>1 1 param являетсяvoid someFunc (МyТemplateТype&& param ) ; / / универсальной ссьmкойРанее я отмечал, что переменные auto также могут быть универсальными ссылками.Чтобы быть более точным, переменные, объявленные с типом aut o & & , являются универсальными ссылками, поскольку имеет место вывод типа и они имеют правильный вид(" Т & & " ) .
Универсальные ссылки auto не так распространены, как универсальные ссылки,используемые в качестве параметров шаблонов функций, но они также время от временивозникают в C++ l l . Гораздо чаще они возникают в C++ l4, поскольку лямбда-выраженияС++ 1 4 могут объявлять параметры aut o & &. Например, если вы хотите написать лямбдавыражение С++ 14 для записи времени, которое занимает вызов произвольной функции,можете сделать это следующим образом:1 74Гnа ва S. Rvalue-ccыnки, семантика перемещений и прямая передачаauto timeFuncinvocation =[ ] ( auto&& func, auto&& .
. . params )/ / С++ 1 4Запуск таймера ;/ / Вызов funcstd: : forward<decltype ( func) > ( func) (std : : forward<decltype (params ) > (params ) . . . / / с params);Оста нов таймера и запись време ни ;);Если ваша реакция на код "std : : forward<decltype (ля -ля-ля ) > " внутри лямбда-выражения - "Что за @#$%?!!'; то, вероятно, вы просто еще не читали раздел 6.3.
Не беспокойтесь о нем. Главное для нас сейчас в этом лямбда-выражении - объявленные как auto&&параметры. func является универсальной ссылкой, которая может быть связана с любымвызываемым объектом, как lvalue, так и rvalue. params представляет собой нуль или несколько универсальных ссылок (т.е. набор параметров, являющихся универсальными ссылками),которые могут быть связаны с любым количеством объектов произвольных типов. В результате, благодаря универсальным ссылкам auto, лямбда-выражение t imeFuncinvocation в состоянии записать время работы почти любого выполнения функции.
(Чтобы разобратьсяв разнице между "любого" и "почти любого'; обратитесь к разделу 5.8.)Имейте в виду, что весь этот раздел - основы универсальных ссылок - являетсялож . . . простите, абстракцией. Лежащая в основе истина, известная как свертывание ссылок (reference collapsing), является темой раздела 5.6. Но истина не делает абстракциюменее полезной. Понимание различий между rvаluе-ссылками и универсальными ссылками поможет вам читать исходные тексты более точно ("Этот Т & & связывается толькос rvalue или с чем утодно?"), а также избегать неоднозначностей при общении с коллегами ("Здесь я использовал универсальную ссылку, а не rvalue-ccылкy. .
."). Оно такжепоможет вам в понимании смысла разделов 5.3 и 5.4, которые опираются на указанноеразличие. Так что примите эту абстракцию, погрузитесь в нее . . . Так же как законы Ньютона (технически не совсем корректные) обычно куда полезнее и проще общей теорииотносительности Эйнштейна ("истины"), так и понятие универсальных ссылок обычнопредпочтительнее для работы, чем детальная информация о свертывании ссылок.Сnедует запомнить•Если параметр шаблона функции имеет тип т & & для выводимого типа Т или еслиобъект объявлен с использованием aut o & & , то параметр или объект является универсальной ссылкой.•Если вид объявления типа не является в точности t уре & & или если вывод типа неимеет места, то t уре & & означает rvalue-ccылкy.•Универсальные ссылки соответствуют rvаluе-ссылкам, если они инициализируютсязначениями rvalue. Они соответствуют lvаluе-ссылкам, если они инициализируютсязначениями lvalue.5.2.Отл ичие универсальных ссылок от rvalue-ccылoк1 75S.3.
Испопьзуйте s td : : move дпя rvalue - ccыno к,дпя универсапьных ссыпока std : : forward-Rvаluе-ссылки связываются только с объектами, являющимися кандидатами для перемещения. Если у вас есть параметр, представляющий собой rvalue-ccылкy, вы знаете,что связанный с ним объект может быть перемещен:class Widget {Widget (Widget&& rhs ) ; / / rhs, определенно, ссьmается на11 объект, который можно перемещать};В этом случае вы захотите передавать подобные объекты друтим функциям таким образом, чтобы разрешить им использовать преимущества "правосторонности". Способ,которым это можно сделать, - привести параметры, связываемые с такими объектами,к rvalue. Как поясняется в разделе 5.
1 , std : : move не просто это делает, это та задача,для которой создана эта функция:class Widget {puЫ ic :// rhs является rvalue-ccьmкoйWidget (Widge t & & rhs ): пате (std: : move (rhs . name) ) ,р (std: : move (rhs .р) ){}".private :std : : string name;std : : shared_ptr<SomeDataStructure> р ;};С друтой стороны, универсальная ссылка (см.
раздел 5.2) может быть связана с объектом, который разрешено перемещать. Универсальные ссылки должны быть приведенык rvalue только тогда, когда они были инициализированы с помощью rvalue. В разделе 5.1разъясняется, что именно это и делает функция std: : for ward:class Widget {puЫ i c :template<typename Т>void setName ( T & & newName )/ / newName является{ name std: : forward<Т> (newName) ; } / / универсальной ссьmкой=};Короче говоря, rvаluе-ссылки при их передаче в друтие функции должны быть безусловно приведены к rvalue (с помощью s t d : : move), так как они всегда связываются с rvalue,а универсальные ссылки должны приводиться к rvalue при их передаче условно (с помощью s t d : : forward), поскольку они только иногда связываются с rvalue.1 76Глава 5. Rv а luе-ссы лки, семантика перемещений и прямая передачаВ разделе 5.1 поясняется, что можно добиться верного поведения rvalue-ccылoк и спомощью s t d : : fo rward, но исходный текст при этом становится многословным, подверженным ошибкам и неидиоматичным, так что вы должны избегать примененияstd : : forward с rvаluе-ссылкам.
Еще худшей является идея применения std : : move к универсальным ссылкам, так как это может привести к неожиданному изменению значенийlvalue (например, локальных переменных):class Widget {puЫ i c :template<typename Т>void setName ( T&& newName ){ narnestd: : move (newName) ;/ / Универсальная ссылка .11 Компилируется, но это=11 очень плохое решение !private :std: : string name ;std: : shared_ptr<SomeDataSt ructure> р ;};std : : string getWidgetName ( ) ; / / Фабричная функцияWidget w;11 nлокальная переменнаяauto n = getWidgetName ( ) ;/ / Перемещение n в w 1w . se tName ( n ) ;11 Значение n теперь неизвестно-Здесь локальная переменная n передается функции w .
s e t N arne . Вызывающий кодможно простить за предположение о том, что эта функция по отношению к n являетсяоперацией чтения. Но поскольку setNarne внутренне использует std : : move для безусловного приведения своего ссылочного параметра к rvalue, значение n может быть перемещено в w . name, и n вернется из вызова setName с неопределенным значением.