С. Мейерс - Эффективный и современный C++ (1114942), страница 18
Текст из файла (страница 18)
Предпочитайте перечиспения с обnастью видимости перечисnениям без таковой81перечисления без области видимости, а именно - при обращении к полям в кортежахC++ l l s t d : : tupl e . Предположим, например, что у нас есть кортеж, содержащий имя,адрес электронной почты и значение репутации пользователя на сайте социальной сети:using Userinfo =std : : tuple<std : : string,std : : s tring,std: : s ize t>//111111Псевдоним типа ; см.
раздел 3 . 3ИмяАдресРепутацияХотя комментарии указывают, что представляет собой каждое поле кортежа, это,вероятно, не слишком полезно, когда вы сталкиваетесь с кодом наподобие следующегов отдельном исходном файле:Userinfo uinfo ;auto val=/ / Объект с типом кортежаstd : : get<l> ( uinfo ) ; / / Получение значения поля 1Как программисту вам приходится отслеживать множество вещей. Вы действительнодолжны помнить, что поле 1 соответствует адресу электронной почты пользователя? Ядумаю, нет.
Использование e nurn без области видимости для сопоставления имен полейс их номерами позволяет избежать необходимости перегружать память:enumUserinfoFields { uiName , uiEmail , ui.Reputation } ;1 1 Как и ранееUserinfo uinfo;auto val=std : : get<uiEmail> (uinfo ) ; / / Значение адресаВсе было бы гораздо сложнее без неявного преобразования значений из Userin foFie ldsв тип st d : : s i ze_t , который является типом, требующимся для s t d : : get.Соответствующий код с применением перечисления с областью видимости существенно многословнее:enum class UserinfoFields { uiName , uiEma i l , uiReputation } ;Userinfo uinfo ;11 Как и ранееauto valstd: : get<static_cast<std: : size_t> (UserinfoFields : : uiEmail) >(uinfo ) ;Эта многословность может быть сокращена с помощью функции, которая принимаетперечислитель и возвращает соответствующее значение типа s t d : : s i ze_t , но это немного сложнее.
s t d : : g e t является шаблоном, так что предоставляемое значение являетсяаргументом шаблона (обратите внимание на применение не круглых, а угловых скобок),так что функция, преобразующая перечислитель в значение s t d : : s i ze_t, должна даватьрезультат во время компиляции. Как поясняется в разделе 3.9, это означает, что нам нужна функция, являющаяся constexpr.82Глава 3.
Переход к современному С++Фактически это должен быть соnstехрr-шаблон функции, поскольку он должен работать с любыми перечислениями. И если мы собираемся делать такое обобщение, то должны обобщить также и возвращаемый тип. Вместо того чтобы возвращать s t d : : s i ze_t,мы должны возвращать базовый тип перечисления. Он доступен с помощью свойстватипа s t d : : unde r l yi ng _ t ype (о свойствах типов рассказывается в разделе 3.3). Наконецмы объявим его как noexcept (см. раздел 3.8), поскольку мы знаем, что он н икогда негенерирует исключений. В результате мы получим шаблон функции t oUType, которыйполучает произвольный перечислитель и может возвращать значение как константу времени компиляции:template<typename Е>constexpr typename std : : underlying_type<E> : : typetoUType ( E enumerator) noexceptreturnstat ic_cast<typenamestd: : underlying_type<E> : : type> ( enumerator) ;В С++ 14 t oUType можно упростить заменой t ypename s t d : : unde r l y ing t ype<E> : : t ypeболее изящным s t d : : unde r l ying_t ype_t (см.
раздел 3.3):template<typename Е>constexpr std : : underlying_type t<E>toUType ( E enumerator) noexcept// С++14return static_cast<std : : underlying_type t<E>> ( enumerato r ) ;Еще более изящный возвращаемый тип auto (см. раздел 1 .3) также корректен в С++ 14:template<typename Е> // С++ 1 4constexpr autotoUType ( E enumerator) noexceptreturn static_cast<std : : underlying_type_t<E>> ( enumerator ) ;Независимо от того, как он написан, шаблон t oUType позволяет нам обратиться к полюкортежа следующим образом:auto val=std: : get<toUТype (UserinfoFielcls : : uiEшail) > (uinfo ) ;Это все же больше, чем достаточно написать при использовании перечисления без области видимости, но зато позволяет избежать загрязнения пространства имен и непреднамеренных преобразований перечислителей.
Во многих случаях вы можете решить, чтонабор нескольких дополнительных символов является разумной ценой за возможность избежать ловушек перечислений, появление которых восходит ко времени, когда вершинойдостижений в цифровых телекоммуникациях был модем со скоростью 2400 бод.3.4. Предпочи тайте перечисления с облас т ью видимости перечислениям без таковой83Следует запомнить•Перечисления в стиле С++98 в настоящее время известны как перечисления безобластей В ИДИ МОСТИ.•Перечислители перечислений с областями видимости видимы только внутри перечислений. Они преобразуются в другие типы только с помощью явных приведений.•Как перечисления с областями видимости, так и без таковых поддерживают указание базового типа. Базовым типом по умолчанию для перечисления с областьювидимости является i nt . Перечисление без области видимости базового типапо умолчанию не имеет.•Перечисления с областями видимости могут быть предварительно объявлены.
Перечисления без областей видимости могут быть предварительно объявлены, только если их объявление указывает базовый тип.3 . 5 . П редпо ч итайте удапенные функциизакрытым неопредепеннымЕсли вы предоставляете код другим разработчикам и хотите предотвратить вызовими некоторой функции, обычно вы просто ее не объявляете. Нет объявления функции - нечего и вызывать. Но иногда С++ объявляет функции вместо вас, и если вы хотите предотвратить вызов таких функций клиентами вашего кода, придется постараться.Эта ситуация возникает только для "специальных функций-членов", т.е. функцийчленов, которые при необходимости С++ генерирует автоматически.
В разделе 3. 1 1 этифункции рассматриваются более подробно, а пока что мы будем беспокоиться толькоо копирующем конструкторе и копирующем операторе присваивания. Эта глава во многом посвящена распространенным практикам С++98, для которых есть более эффективная замена в С++ 1 1 , а в С++98, когда вы хотите подавить применение функции-члена,это почти всегда копирующий конструктор, оператор присваивания или они оба.Подход С++98 для предотвращения применения этих функций состоит в объявленииих как private без предоставления определений.
Например, вблизи с основанием иерархии потоков ввода-вывода в стандартной библиотеке С++ находится шаблонный классba s i c_ios. Все классы потоков наследуют (возможно, косвенно) этот класс. Копированиепотоков ввода-вывода нежелательно, поскольку не совсем очевидно, что же должна делать такая операция. Объект i s t r earn, например, представляет поток входных значений,одни из которых могут уже быть считаны, а другие могут потенциально быть считаныпозже. Если копировать такой поток, то должно ли это повлечь копирование всех считанных значений, а также значений, которые будут считаны в будущем� Простейшийспособ разобраться в таких вопросах - объявить их несуществующими.
Именно это делает запрет на копирование потоков.Чтобы сделать классы потоков некопируемыми, bas i c_ios в С++98 объявлен следующим образом (включая комментарии):84Гnава З . Переход к современному Ctttemplate <class charT, class traitsclass bas ic ios : puЬlic ios basepuЬlic :char traits<charT> >private :/ / Не определенbasic ios ( const basic_ios& ) ;basic ios& operator= ( const basic_ios & ) ; / / Не определен};Объявление этих функций как pri vate предотвращает их вызов клиентами. Умышленное отсутствие их определений означает, что если код, все еще имеющий к ним доступ (т.е. функции-члены или друзья класса), ими воспользуется, то компоновка (редактирование связей) будет неудачной из-за отсутствия определений функций.В С++ 1 1 имеется лучший способ достичь по сути того же самого: воспользоватьсяконструкцией "= delete': чтобы пометить копирующий конструктор и копирующее присваивание как удаленные функции.
Вот та же часть b a s i c ios в С++ 1 1 :_template <class charT, class traitsclass basic ios : puЫic ios basepuЫic :char traits<charT> >basic ios ( const bas ic_ios & ) = delete ;basic ios& operator= ( const basic ios & )delete ;};Отличие удаления этих функций от их объявления как pri vate может показаться большевопросом вкуса, чем чем-то иным, но на самом деле в это заложено больше, чем вы думаете.Удаленные функции не могут использоваться н икоим образом, так что даже код функциичлена или функций, объявленных как friend, не будет компилироваться, если попытаетсякопировать объекты b a s i c_ ios. Это существенное улучшение по сравнению с поведениемС++98, где такое некорректное применение функций не диагностируется до компоновки.По соглашению удаленные функции объявляются как puЫic, а не private. Тому естьпричина.