С. Мейерс - Эффективный и современный C++ (1114942), страница 49
Текст из файла (страница 49)
д .private :std : : striпg паmе ;};Красота! Ну ладно, красота в основном для тех, кто фетишизирует метапрограммирование с использованием шаблонов, но факт остается фактом: этот подход не толькосправляется с работой, но и делает это с чрезвычайным апломбом. Применение прямойпередачи предполагает высокую эффективность, а управление сочетанием универсальных ссылок и перегрузки вместо их запрета позволяет применять этот метод в обстоятельствах (таких, как разработка конструкторов), когда перегрузка неизбежна.Ко м п ромиссыПервые три рассмотренные в данном разделе метода - отказ от перегрузки, передачаcoпst Т& и передача по значению - указывают тип каждого параметра в вызываемойфункции или функциях. Последние два метода - диспетчеризация дескрипторов и ограничения шаблонов - используют прямую передачу, а следовательно, типы параметровне указывают.
Это фундаментальное решение - указывать типы или нет - имеет своиследствия.Как правило, прямая передача более эффективна, потому что позволяет избежать создания временных объектов исключительно с целью соответствия типу объявления параметра. В случае конструктора Person прямая передача допускает передачу строковоголитерала, такого как "Nancy", непосредственно в конструктор для std : : st riпg внутриPersoп, в то время как методы, не использующие прямой передачи, вынуждены создавать временный объект std : : striпg из строкового литерала для удовлетворения спецификации параметра конструктора Persoп.Но прямая передача имеет свои недостатки. Один из них тот, что некоторые видыаргументов не могут быть переданными прямой передачей, несмотря на то что они могутбыть переданы функциям, принимающим конкретные типы.
Эти сбои прямой передачиисследуются в разделе 5.8.Второй неприятностью является запутанность сообщений об ошибках, когда клиенты передают недопустимые аргументы. Предположим, например, что клиент, создающий200Гл ава 5. Rvalue-ccыnки, семантика перемещений и прямая передачаобъект Pers on, передает строковый литерал, составленный из символов cha r l б_t (типС++ 1 1 для представления 1 6-разрядных символов) вместо char (из которых состоитstd : : st ring ) :Person p ( u"Konrad Zuse" ) ; // "Konrad Zuse" состоит иэ// символов типа const charlб tПри использовании первых трех из рассмотренных в данном разделе подходовкомпиляторы увидят, что доступные конструкторы могут принимать либо i n t , либоstd : : s t r ing, и выведут более или менее понятное сообщение об ошибке, поясняющее,что не существует преобразования из const charl б_t [ 12 ) в int или s t d : : s t r i ng.Однако при подходе с использованием прямой передачи массив const char l б tсвязывается с параметром конструктора без замечаний и жалоб.
Оттуда он передаетсяконструктору члена-данных типа std : : str ing класса Person, и только в этот момент обнаруживается несоответствие между тем, что было передано (массив const cha rl 6_t ) ,и тем, что требовалось (любой тип, приемлемый для конструктора std : : st ring ) . В результате получается впечатляющее сообщение об ошибке. Так, в одном из компиляторовоно состояло более чем из 1 60 строк!В этом примере универсальная ссылка передается только один раз (из конструктораPerson в конструктор s t d : : s t r ing ), но чем более сложна система, тем больше вероятность того, что универсальная ссылка передается через несколько слоев вызовов функцийдо того, как достигнет точки, в которой определяется приемлемость типа аргумента (илитипов).
Чем большее количество раз будет передаваться универсальная ссылка, тем болеенепонятными и громоздкими будут выглядеть сообщения об ошибках, если что-то пойдетне так. Многие разработчики считают, что одно это является основанием для примененияуниверсальных ссылок только там, где особенно важна производительность.В случае класса Person мы знаем, что параметр универсальной ссылки передающейфункции должен выступать в роли инициализатора для std : : st r i ng, так что мы можемиспользовать stat ic assert для того, чтобы убедиться в его пригодности для этой роли.Свойство типа std : : i s_cons t ruct iЫe выполняет проверку времени компиляции того,может ли объект одного типа быть построен из объекта (или множества объектов) другого типа (или множества типов), так что написать такую проверку несложно:_class Person {puЫ ic :11 Как и ранееtemplate<typename Т,typename = std : : enaЫe if t <1 std : : is_base_of<Person, std: : decay_t<T>> : : value&&! std: : i s_integra l<std : : remove_reference_t<T>> : : value>>expl icit Person ( T&& n ): name ( std: : forward<T> ( n ) )5.5.
Знакомство с аnьтернативами перегрузки дnи универсаnьных ссыnок2011 1 Проверка возможности создания std: : string из Тstatic_assert (std: : is constructiЫe<std: : string, Т> : : value ,_"П8рам8'1'р nнеможет использоваться Д11Я ""конаrруиро:вания а std: : string");11 Здесь идет код обычного конструктора1 1 Остальная часть класса Person ( как ранее)};В результате при попытке клиентского кода создать объект Person из типа, непригодного для построения std : : s t ri ng, будет выводиться указанное сообщение об ошибке. К сожалению, в этом примере s t a t ic _a s s e rt находится в теле конструктора, а передающий код, являясь частью списка инициализации членов, ему предшествует.
Использовавшиеся мною компиляторы выводят ясное и понятное сообщение об ошибкеот s t a t i c_a s sert, но только после обычных сообщений (всех этих 160 с лишним строк).Следует запомнит ь•Альтернативы комбинации универсальных ссылок и перегрузки включаютиспользование различных имен, передачу параметров как lvalue-ccылoкна c o n s t, передачу параметров по значению и использование диспетчеризациидескрипторов.•Ограничение шаблонов с помощью s t d : : e n a Ы e _ i f позволяет использоватьуниверсальные ссылки и перегрузки совместно, но управляет условиями, прикоторых компиляторы могут использовать перегрузки с универсальнымиссылками.•Параметры, представляющие собой универсальные ссылки, часто имеютпреимущества высокой эффективности, но обычно их недостатком являетсясложность использования.S .6.
Свертывание ссыл окВ разделе 5. 1 говорилось, что, когда аргумент передается в шаблонную функцию, выведенный для параметра шаблона тип указывает, является ли аргумент lvalue или rvalue.В разделе не было упомянуто, что это происходит только тогда, когда аргумент используется для инициализации параметра, являющегося универсальной ссылкой, но тому естьуважительная причина: универсальные ссылки появились только в разделе 5.2. Вместеэти наблюдения об универсальных ссылках и кодировании lvalue/rvalue означают, чтодля шаблонаtemplate<typename Т>void func ( T & & param) ;202Глава 5.
Rvаluе-ссылки, семантика перемещений и прямая передачавыведенный параметр шаблона Т будет включать информацию о том, был ли переданныйв param аргумент lvalue или rvalue.Механизм этого кодирования прост. Если в качестве аргумента передается lvalue, твыводится как \vа\uе-ссылка. При передаче rvalue вывод типа приводит к тому, что т неявляется ссылкой. (Обратите внимание: lvalue кодируются как \vаluе-ссылки, но rvalueкодируются как не ссылки.) Следовательно:Widget widgetFactory ( ) ; 1 1Widget w ;11func (w) ;1111func (widgetFactory ( ) ) ; 1 111Функция, возвращающая rvalueПеременная ( lva lue )Вызов функции с lvalue ; тип тпредставляет собой Widget&Вызов функции с rvalue ; тип тпредставляет собой WidgetВ обоих вызовах func передается W i dget, но так как один W i dget является lvalue,а второй представляет собой rvalue, для параметра шаблона Т выводятся разныетипы. Это, как вы вскоре увидите, и определяет, чем становятся универсальные ссыл ки: rvalue- или lvа\uе-ссылками; а кроме того, это механизм, лежащий в основе работыs t d : : forward.Прежде чем более внимательно рассмотреть std : : forward и универсальные ссылки,мы должны заметить, что ссылка на ссылку в С++ не существует.
Попытайтесь объявитьее, и компилятор вынесет вам строгий выговор:intх;auto& & r x=х;/ / Ошибка ! Объявлять ссылки н а ссьщки нельзяНо рассмотрим, что произойдет, если передать lvalue шаблону функции, принимающему универсальную ссылку:template<typename Т>void func ( T & & param) ; / / Как и ранееfunc (w) ;1 1 Вызов func с lvalue ;/ / Т выводится как Widget&Если мы возьмем тип, выведенный для Т (т.е. Widge t & ) и используем его для инстанцирования шаблона, то получим следующее:void func (Widget& && param) ;Ссылка на ссылку! Но компиляторы не возражают.