С. Мейерс - Эффективный и современный C++ (1114942), страница 21
Текст из файла (страница 21)
Перегрузкадля lvаluе-ссылки возвращает lvalue-ccылкy (т.е. lvalue), а перегрузка для rvаluе-ссылкивозвращает rvalue-ccылкy (которая, как возвращаемый тип функции, является rvalue).Это означает, что клиентский код ведет теперь себя так, как мы и хотели:auto val s lauto val s 2=/ / Вызывает lvаluе - перегрузку11 Widget : : data, va lsl11 создается копированиемmakeWidget ( ) . data ( ) ; / / Вызывает rvаluе - перегрузку11 Widget : : data, val s 2/ / создается перемещениемw . data ( ) ;Это, конечно, хорошо, но не позвольте теплому сиянию этого хэппи-энда отвлечь васот истинной цели этого раздела. Эта цель в том, чтобы убедить вас, что всякий раз, когдавы объявляете в производном классе функцию, предназначенную для перекрытия виртуальной функции базового класса, вы не забывали делать это с использованием ключевогослова overr i de.Кстати, если функция-член использует ссылочный квалификатор, все перегрузки этойфункции также должны использовать его.
Это связано с тем, что перегрузки без этихквалификаторов могут вызываться как для объектов lvalue, так и для объектов rvalue.Такие перегрузки будут конкурировать с перегрузками, имеющими ссылочные квалификаторы, так что все вызовы функции будут неоднозначными.if rule"правило, согласно которому разрешены любые преобразования кода, не изменяющие наблюдаемое 11оведение программы. Примеч.
ред.4 "As--94Глава 3. Переход к современному С++Сл едует зап омнить•Объявляйте перекрывающие функции как over ride.•Ссылочные квалификаторы функции-члена позволяют по-разному рассматривать lvalue- и rvаluе-объекты ( * th i s ) .3 .7.
Предпочитайте итераторы const_iteratorитераторам i tera torИтераторы const _ i t e ra t o r представляют собой SТL-эквивалент указателя на con s t .Они указывают н а значения, которые н е могут быть изменены. Стандартная практикаприменения const там, где это только возможно, требует применения c o n s t_ i t e ra t o rвезде, где нужен итератор, н о не требуется изменять то, н а что этот итератор указывает.Это верно как для С++98, так и для C++ l l , но в С++98 поддержка const_i t e ra t o rносит половинчатый характер. Такие итераторы н е так легко создавать, а если у вас ужеимеется такой итератор, его использование весьма ограничено.
Предположим, например,что вы хотите выполнить в s t d : : vect o r < i n t > поиск первого встречающегося значения1983 (год, когда название языка программирования "С с классами" сменилось на С++),а затем вставить в это место значение 1 998 (год принятия первого ISО-стандарта С++).Если в векторе нет значения 1 983, вставка выполняется в конец вектора. При использовании итераторов i t e ra t o r в С++98 сделать описанное просто:std: : vector<int> value s ;s t d : : vector<int > : : iterator i tstd : : find ( values .
begin ( ) , value s . end ( ) , 1 9 8 3 ) ;values . insert ( it , 1 998 ) ;=Но i t e ra t o r - в данном слу<1ае не совсем верный выбор, поскольку этот код неизменяет объект, на который указывает i t e r a t or. Переделка кода для использованияconst _ i t e ra t o r должна быть тривиальной задачей" . но не в С++98. Вот один из подходов, концептуально надежный, но все еще не совсем корректный:typedef std : : vector<int> : : iterator IterT;std : : vector<int> : : const iterator Consti terT;std: : vector<int> value s ;Const i terT c istd : : find ( static_cast<ConstiterТ> (values . begin ( ) ) ,static_cast<ConstiterT> (values . end ( ) ) ,1 98 3 ) ;=3.7. Предпочитайте итераторы const_iterator итераторам iterator95values . insert ( static_cast<IterТ> (ci) ,1998 ) ;/ / Может не/ / компилироваться !Конструкции t ypede f применять, конечно, необязательно, но они облегчают написание приведений.
(Если вы удивляетесь, почему я использую t ypedef, а не следую собственному совету из раздела 3.3 и не применяю объявлений псевдонимов, то я напомню,что в этом примере демонстрируется код С++98, а объявления псевдонимов - новаявозможность в С++ 1 1 .)Приведения в вызове s t d : : f i nd присутствуют потому, что values является неконстантным контейнером, а в С++98 нет простого способа получить константный итератор из неконстантного контейнера.
Приведения не являются строго необходимыми, таккак можно получить const _ iterator другими способами (например, вы можете связатьvalues с переменной, являющейся ссылкой на константный объект, а затем использоватьее в своем коде вместо val ues), но к какому бы способу вы ни прибегли, процесс получения const _ i t e rator, указывающего на элементы неконстантного контейнера, включаетопределенное количество "кривизны':После того как вы получаете const _ i terator, ситуация ничуть не улучшается, поскольку в С++98 местоположения для вставки (и удаления) могут указывать только неконстантные итераторы iterator. Итераторы const_iterator для этого неприменимы.Вот почему в приведенном выше коде я выполняю приведение const _ i terator (которыйя с таким трудом получил из s t d : : f ind) в i t e rator: передача const_ i terator в функцию i nsert не будет компилироваться.Честно говоря, показанный мной код может не скомпилироваться, поскольку не существует переносимого преобразования const_iterator в iterat or, даже с помощьюstatic_cast.
Может не сработать даже семантическая кувалда re interpret _cast. (Этоне ограничение С++98. Это справедливо и для C++ l l . const_iterator просто не преобразуется в i t e rator - неважно, насколько простым кажется такое преобразование.)Есть несколько переносимых способов сгенерировать iterator, который указывает на тоже, на что и const _ i terator, но они не очевидны, не универсальны и не стоят того, чтобы рассматривать их в данной книге. Кроме того, я надеюсь, что теперь понятна моя позиция: const_ i t e rator причиняли так много неприятностей в С++98, что редко стоилитого, чтобы о них беспокоиться и их использовать.
По большому счету программистывсегда использовали const не где это только возможно, а только там, где это практично,а в С++98 const _ i terator особо практичным не является.Все изменилось с появлением C++ l l . Теперь const_iterator легко получить и легкоиспользовать. Функции-члены контейнера cbegi n и cend возвращают const_ i t eratorдаже для неконстантных контейнеров, а функции-члены STL, которые применяют итераторы для указания позиций (например, insert и erase ) , в действительности используютитераторы const _ iterator. Переделка кода С++98, который использовал i terat or, в кодс const_iterator в С++ 1 1 воистину тривиальна:std: : vector<int> values;auto96itstd : : find (values . cЬegin ( ) ,// Какиранее=Глава 3.
Переход к современному С++11 Используемcbeginvalues . cend ( ) , 1 98 3 ) ; / / и cendvalues . insert ( it , 1998 ) ;Теперь код, использующий const _ i terator, стал действительно практичным!Почти единственной ситуацией, в которой поддержка С++ 1 1 для const _ i t e ratorоказывается недостаточной, является ситуация, когда вы хотите написать максимальнообобщенный библиотечный код.
Такой код принимает во внимание то, что некоторыеконтейнеры и контейнерообразные структуры данных предоставляют функции begi nи end ( а также cbeg in, cend, rbegin и т.д.) как функции, н е являющиеся членами. Этопроисходит, например, в случае встроенных массивов или при использовании некоторыхбиблиотек сторонних производителей с интерфейсами, состоящими только из свободныхфункций. Максимально обобщенный код использует функции, не являющиеся членами,а не предполагает наличие функций-членов.Например, мы можем обобщить код, с которым только что работали, в шаблонfindAndinsert следующим образом:template<typename С, typename V>void findAndlnsert (C& container,/ / В container находитconst V& ta rgetVa l , / / первое значениеconst V& insertVa l ) / / targetVa l , эатем11 вставляет insertValusing std: : cЬegin;using std: : c:end ;auto it=std : : find ( cЬegin ( container ) , / / Не член cbegincend ( containe r ) ,/ / Не член cendtargetVa l ) ;container .
insert ( i t , insertVa l ) ;Этот код прекрасно работает в С++ 14, но, к сожалению, не в С++ 1 1 . Из-за недосмотрав процессе стандартизации в С++ 1 1 добавлены не являющиеся членами функции beg i nи end, но не были добавлены cbegin, cend, rbegin, rend, crbegin и crend. С++ 14 исправляет это упущение.Если вы используете С++ 1 1, хотите написать максимально обобщенный код и ни одна изиспользуемых вами библиотек не предоставляет отсутствующие шаблоны для cbegin и подобных функций, не являющихся шаблонами, можете легко написать собственные реализации.
Например, вот как выглядит реализация функции cbegin, не являющейся членом:template <class С>auto cbegin ( const С& container ) - >decltype ( std : : begin ( container ) ){return std : : begin ( container ) ; / / См. пояснения нижеВы удивлены тем, что эта функция cbegin не вызывает функцию-член cbeg in, не такли� Я тоже был удивлен.