С. Мейерс - Эффективный и современный C++ (1114942), страница 19
Текст из файла (страница 19)
Когда код клиента пытается использовать функцию-член, С++ проверяет доступность до проверки состояния удаленности. Когда клиентский код пытается использовать функцию, объявленную как private, некоторые компиляторы жалуются на то,что это закрытая функция, несмотря на то что доступность функции никак не влияетна возможность ее использования. Стоит принять это во внимание при пересмотре старого кода и замене не определенных функций-членов, объявленных как pri vate, удаленными, поскольку объявление удаленных функций как puЫ i c в общем случае приводитк более корректным сообщениям об ошибках.Важным преимуществом удаленных функций является то, что удаленной может бытьлюбая функция, в то время как быть pri vate могут только функции-члены. Предположим, например, что у нас есть функция, не являющаяся членом, которая принимаетцелочисленное значение и сообщает, является ли оно "счастливым числом":bool isLucky ( int nшnЬer} ;3.5. Предпочитайте удаленные функции закрытым неоп редел енным85То, что С++ является наследником С, означает, что почти любой тип, который можнорассматривать как отчасти целочисленный, будет неявно преобразовываться в int, нонекоторые компилируемые вызовы могут не иметь смысла:if ( i sLucky ( ' а ' ) ) ...if ( isLucky ( t rue ) )if ( isLucky ( З .
5 ) )_-·11 Является ли ' а ' счастливым числом?/ / Является ли true счастливым числом?11 Следует ли вьшолнить усечение до 311 перед проверкой на "счастливость " ?Если счастливые числа действительно должны быть только целыми числами, хотелосьбы предотвратить такие вызовы, как показано выше.Один из способов достичь этого - создание удаленных перегрузок для типов, которые мы хотим отфильтровывать:boolboolboolboolisLucky ( int nurnЬer ) ;isLucky ( char )= dslete;i sLucky ( bool )= clelete ;isLucky (douЬle) = clelete;11111111Исходная функцияОтвергаем символыОтвергаем булевы значенияОтвергаем douЫe и float(Комментарий у перегрузки с douЬle, который гласит, что отвергаются как douЬle,так и f l oat, может вас удивить, но ваше удивление исчезнет, как только вы вспомните,что при выборе между преобразованием f l oat в i nt и float в douЫe С++ предпочитаетпреобразование в douЬle.
Вызов i sLucky со значением типа f l oat приведет к вызовуперегрузки с типом douЬle, а не int. Вернее, постарается привести. Тот факт, что даннаяперегрузка является удаленной, не позволит скомпилировать такой вызов.)Хотя удаленные функции использовать нельзя, они являются частью вашей программы. Как таковые они принимаются во внимание при разрешении перегрузки.
Вот почемупри указанных выше объявлениях нежелательные вызовы i sLucky будут отклонены:if ( i s Lucky ( ' а ' ) ) ... / / Ошибка ! Вызов удаленной функцииif ( isLucky ( true ) ) ... / / Ошибка !if ( i s1ucky ( 3 . 5 f ) ) ... 11 Ошибка !Еще один трюк, который могут выполнять удаленные функции (а функции-члены,объявленные как private - нет), заключается в предотвращении использования инстанцирований шаблонов, которые должны быть запрещены.
Предположим, например,что нам нужен шаблон, который работает со встроенными указателями (несмотря на совет из главы 4, "Интеллектуальные указатели", предпочитать интеллектуальные указателивстроенным):template<typename Т>void proces s Pointer (T* ptr ) ;В мире указателей есть два особых случая. Один из них - указатели void*, посколькуих нельзя разыменовывать, увеличивать или уменьшать и т.д. Второй - указатели char*,поскольку они обычно представляют указатели на С-строки, а не на отдельные символы.Эти особые случаи часто требуют особой обработки; будем считать, что в случае шаблона proce s s Pointer эта особая обработка заключается в том, чтобы отвергнуть вызовы86Глава 3 .
Переход к современному С++с такими типами (т.е. должно быть невозможно вызвать proce s s Po i nt e r с указателямитипа void* или cha r* ) .Это легко сделать. Достаточно удалить эти инстанцирования:template<>void processPo inter<void> ( void* )template<>void processPointer<char> ( char* )delete;=delete ;Теперь, если вызов proce s s Po i n t e r с указателями vo i d* или cha r* является некорректным, вероятно, таковым же является и вызов с указателями const void* илиconst char*, так что эти инстанцирования обычно также следует удалить:template<>void processPointer<const void> ( cons t void* )template<>void processPointer<const char> ( const char * )de lete ;=delete;И если вы действительно хотите быть последовательными, то вы также удалите перегрузки const volat i l e void* и const vola t i l e cha r * , а затем приступите к работе над перегрузками для указателей на другие стандартные типы символов: wchar_ t,cha r l б t и cha r32 t .Интересно, что если у вас есть шаблон функции внутри класса и вы попытаетесь отключить некоторые инстанцирования, объявляя их pri vat e (в духе классического соглашения С++98), то у вас ничего не получится, потому что невозможно дать специализации шаблона функции-члена другой уровень доступа, отличный от доступа в главномшаблоне.
Например, если p roce s s Po i n t e r представляет собой шаблон функции-членав Widget и вы хотите отключить вызовы для указателей void*, то вот как будет выглядеть (не компилируемый) подход С++98:class WidgetpuЫic :template<typename Т>void processPointer ( T* ptr ){ ... }private :template<>void processPointer<void> ( void* ) ;};11 Ошибка !Проблема заключается в том, что специализации шаблона должны быть написаныв области видимости пространства имен, а не области видимости класса. Эта проблема не возникает для удаленных функций, поскольку они не нуждаются в другом уровне доступа.
Они могут быть удалены за пределами класса (а следовательно, в областивидимости пространства имен):3.5. Предпочитайте уда л енные функции закрытым неопределенным87class WidgetpuЫ i c :template<typenarne Т>void proce s s Pointer ( T * pt r ){ ... ));template<>1 1 Все еще puЫic, но удаленнаяvoid Widget : : proces s Pointer<void> (void* )delet e ;=Истина заключается в том, что практика С++98 объявления функций private без ихопределения в действительности была попыткой добиться того, для чего на самом делесозданы удаленные функции С++ 1 1 .
Будучи всего лишь имитацией, подход С++98 работает не так хорошо, как вещь, которую он имитирует. Он не работает вне классов, невсегда работает внутри классов, а когда работает, то может не работать до компоновки.Так что лучше придерживаться удаленных функций.Следует запомн ить•Предпочитайте удаленные функции закрытым функциям без определений.•Удаленной может быть любая функция, включая функции, не являющиеся членами, и инстанцирования шаблонов.3 .6. О бъявпяйте перекрывающие функции как overrideМир объектно-ориентированного программирования С++ вращается вокруг классов,наследования и виртуальных функций.
Среди наиболее фундаментальных идей этогомира - та, что реализации виртуальных функций в производных классах перекрывают(override) реализации их коллег в базовых классах. Понимание того, насколько легко всеможет пойти наперекосяк при перекрытии функций, попросту обескураживает.
Полноеощущение, что эта часть языка создавалась как иллюстрация к законам Мерфи.Очень часто термин "перекрытие" путают с термином "перегрузка", хотя они совершенно не связаны друг с другом. Поэтому позвольте мне пояснить, что перекрытие виртуальной функции - это то, что делает возможным вызов функции производного классачерез интерфейс базового класса:class Base {puЫic :virtual void doWork ( ) ;1111);class Derived : puЬlic Base {88Глава З .
Переход к современному С++Виртуальная функциябазового классаpuЫ i c :virtual void doWork ( ) ;11 Перекрывает Base : : doWork11 Ключевое с.лова virtual};/ / здесь не обязательноеstd : : unique_pt r<Бase> upb11 Указатель на базовый классstd : :make_unique<Derived> ( ) ; / / указывает на объект11 производного класса ;11 см . std : : make_unique11 в разделе 4 .
4=/ / Вызов doWork через указатель11 на базовый класс ; вызывается11 функция производного классаupb->doWork ( ) ;Для осуществления перекрытия требуется выполнение нескольких условий.•Функция базового класса должна быть виртуальной.•Имена функций в базовом и производном классах должны быть одинаковыми (заисключением деструктора).•Типы параметров функций в базовом и производном классах должны быть одинаковыми.•Константность функций в базовом и производном классах должна совпадать.•Возвращаемые типы и спецификации исключений функций в базовом и производном классах должны быть совместимыми.К этим ограничениям, являющимся частью С++98, С++ 1 1 добавляет еще одно.•Ссьточные квалификаторы функций должны быть идентичными.