Анимация
JavaScript
|
Главная Библионтека Задача 8. Дружественные шаблоны Сложность: 4 Если вы захотите объявить специализацию шаблона функции в качестве друга, как вы поступите? Согласно стандарту С++, вы должны воспользоваться одним из двух синтаксически корректных способов. В мире же реальных компиляторов один способ поддерживается слабо, зато второй работает со всеми текущими версиями распространенных компиляторов... за исключением одного. Допустим, у нас есть шаблон функции, которая делает нечто с объектом. В частности, рассмотрим шаблон функции boost: :checked delete из [Boost], который удаляет переданный ему объект; среди прочего, этот шаблон вызывает деструктор объекта. namespace boost { tempiate<typename т> void checked delete( T* x ) { ... некоторые действия ... delete x; Допустим, что теперь вы хотите использовать этот шаблон с классом, у которого интересующая операция (в случае шаблона boost: :checked delete ....... деструктор) оказывается закрытой. class Test { -TestO { } Закрытая (private) функция! Test* t = new Test; boost::checked.....delete( t ); Ошибка: деструктор класса Test закрытый, поэтому он не может быть вызван в шаблоне checked delete Решение простое: надо сделать checked delete другом класса Test. (Единственная альтернатива заключается в том, чтобы сделать деструктор класса Test открытым.) Что может быть проще? И в самом деле, в стандарте С++ предусмотрено два законных и простых способа сделать это. Если, конечно, ваш компилятор будет не против... Вопрос для новичка 1. Укажите очевидный, удовлетворяющий стандарту синтаксис объявления boost: :checked delete другом класса rest. Вопрос для профессионала 2. Почему очевидный способ на практике оказывается ненадежным? Укажите более надежный вариант. Решение Эта задача - проверка на соответствие теории и практики. Сделать другом шаблон из другого пространства имен - это гораздо легче сказать (в стандарте), чем сделать (с использованием реального компилятора). В связи с этим у меня для вас есть одна хорошая новость, одна плохая и, чтобы подбодрить вас, еще одна хорошая новость. • Хорошая новость: сушествует два отличных стандартных способа выполнить поставленную задачу, причем их синтаксис вполне естественен. • Плохая новость: ни один из них не работает на всех современных компиляторах. Даже известные как наиболее соответствующие стандарту компиляторы не позволят вам воспользоваться как минимум одним, а то и обоими стандартными способами. • Хорошая новость: один из способов работает на всех проверенных мною компиляторах - кроме gcc. Итак, приступим. Исходная попытка 1. Укажите очевидный, удовлетворяющий стандарту синтаксис объявления boost: :checked delete другом класса Test. Эта задача возникла как следствие вопроса, который задал в Usenet Стефан Бори (Stephan Born), когда пытался добиться того же, чего хотим добиться и мы. Его проблема заключалась в том, что когда он пытался сделать специализацию boost:: checked del ete другом своего класса Test, код не работал на его компиляторе. Ниже представлен его исходный текст. Пример 8-1: попытка добиться дружбы class Test { -Test О { } friend void boost::checked delete( Test* x ); Увы, этот код не работал не только на компиляторе автора вопроса, ио и на ряде других компиляторов. Коротко говоря, объявление в примере 8-1 обладает следующими характеристиками: • оно соответствует стандарту, но полагается на "темный угол" языка; • оно отвергается многими компиляторами, в том числе очень хорошими; • его легко исправить таким образом, чтобы он перестал зависеть от "темных углов" и работал практически на всех современных компиляторах (кроме gcc). Я готов приступить к пояснению четырех способов объявления друзей в С++. Это просто. Я также хочу показать вам, как поступают реальные компиляторы, и завершить рассмотрение этого вопроса написанием наиболее переносимого кода. В "темных углах" 2. Почему очевидный способ на практике оказывается неиалежны.м? Укажите более надежный вариант. При объявлении друзей имеется четыре возможности (перечисленные в [С-Ы-ОЗ], §14.5.3). Они сводятся к следующему. Когда вы объявляете друга, не пользуясь ключевым словом template: 1. Если имя друга выглядит как имя специализации шаблона с явными аргументами (например, Name<SomeType>), то другом является указанная специализация шаблона. 2. Иначе, если имя друга квалифицировано с использованием имени класса или пространства имен (например Some:: Name) этот класс или пространство имен содержит подходящую нешаблонную функцию, то другом является эта функция. 3. Иначе, если имя друга квалифицировано с использованием имени класса или пространства имен (например Some: : Name) И этот класс или пространство имен содержит подходящий шаблон функции (с выводимыми аргументами шаблона) то другом является специализация этого шаблона функции. 4. В противном случае имя должно быть неквалифицированным и объявляет (возможно, повторно) обычную (нешаблонную) функцию. Понятно, что второй и четвертый пункты соответствуют нешаблонным сущностям, так что для объявления специализации шаблона в качестве друга у нас есть только два варианта: либо подогнать свой код к случаю 1, либо - к случаю 3. В нашем случае это выглядит следующим образом. исходный текст корректен, так как соответствует случаю 3 friend void boost::checked delete( Test* x ); добавляем "<Test.>"; код при этом остается корректен, но соответствует случаю 1 friend void boost::checked delete<Test>( Test* x ); Первый вариант по сути представляет собой сокращение второго... но только если имя квалифицировано (в нашем случае - использованием boost::) и в указанной области видимости нет подходящих нешаблонных функций. Хотя оба объявления корректны, первое использует то, что я называю "темными углами" языка, в них часто путаются не только программисты, но и компиляторы. Я могу назвать по крайней мере три причины, по которым следует избегать объявления такого вида, несмотря на его техническую корректность. Причина 1: не всегда работает Как уже упоминалось, синтаксис, используемый в третьем правиле, по сути представляет собой сокращение синтаксиса, используемого в первом правиле; в нем опускаются явные аргументы шаблона в угловых скобках. Однако такое сокращение работает только тогда, когда имя квалифицировано и указан класс или пространство имен, которые ие содержат подходящей нешаблонной функции. В частности, если пространство имен содержит (или получит позже) подходящую нешаблонную функцию, то будет выбрана именно она, поскольку ее наличие означает, что реализуется случай 2, а не 3. Достаточно тонко и неожиданно, не правда ли? Здесь очень легко допустить ошибку, так что лучше избегать использования таких тонкостей. Причина 2: удивляет программистов Случай 3 оказывается неожиданным и удивительным для программистов, которые пытаются разобраться в коде и понять, как он работает. Рассмотрим, например, весьма незначительно отличающийся вариант кода - все отличие состоит в том, что я убрал квалифицирующую часть boost::. имя стало неквалифицированным, а это означает нечто совершенно иное class Test { -TestO { } friend void checked delete( Test* x ); Если вы опустите boost:: (т.е. сделаете вызов неквалифицированным), то оказывается, что эта ситуация соответствует совсем другому случаю, а именно - случаю 4, который вообще не работает для шаблонов. Держу пари, что любой программист 0 1 2 3 4 5 6 7 8 9 10 11 12 13 [ 14 ] 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |