
Динамо-машины Метод Сократа
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 86 87 88 89 90
Плохая новость: ни один из них не работает на всех современных компиляторах. Даже известные как наиболее соответствующие стандарту компиляторы не позволят вам воспользоваться как минимум одним, а то и обоими стандартными способами.
Хорошая новость: один из способов работает на всех проверенных мною компиляторах - кроме 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. Почему очевидный способ на практике оказывается неиадежны.м? Укажите более надежный вариант.
При объявлении друзей имеется четыре возможности (перечисленные в [С-Ы-ОЗ], sect;14.5.3). Они сводятся к следующему.
Когда вы объявляете друга, не пользуясь ключевым словом template:
1. Если имя друга выглядит как имя специализации шаблона с явными аргументами (например, Name lt;SomeType gt;),
то другом является указанная специализация шаблона.
2. Иначе, если имя друга квалифицировано с использованием имени класса или пространства имен (например Some::Name) этот класс или пространство имен содержит подходящую нешаблонную функцию,
то другом является эта функция.
3. Иначе, если имя друга квалифицировано с использованием имени класса или пространства имен (например Some: : Name) И этот класс или пространство имен содержит подходящий шаблон функции (с выводимыми аргументами шаблона)
то другом является специализация этого шаблона функции.
4. В противном случае имя должно быть неквалифицированным и объявляет (возможно, повторно) обычную (нешаблонную) функцию.
Понятно, что второй и четвертый пункты соответствуют нешаблонным сущностям, так что для объявления специализации шаблона в качестве друга у нас есть только два варианта: либо подогнать свой код к случаю 1, либо - к случаю 3. В нашем случае это выглядит следующим образом.
исходный текст корректен, так как соответствует случаю 3 friend void boost::checked delete( Test* x );
добавляем lt;Test gt; ; код при этом остается корректен, но соответствует случаю 1
friend void boost::checked delete lt;Test gt;( Test* x );
Первый вариант по сути представляет собой сокращение второго... но только если имя квалифицировано (в нашем случае - использованием boost::) и в указанной области видимости нет подходящих нешаблонных функций. Хотя оба объявления корректны, первое использует то, что я называю темными углами языка, в них часто путаются не только программисты, но и компиляторы. Я могу назвать по крайней мере три причины, по которым следует избегать объявления такого вида, несмотря на его техническую корректность.
Причина 1: не всегда работает
Как уже упоминалось, синтаксис, используемый в третьем правиле, по сути представляет собой сокращение синтаксиса, используемого в первом правиле; в нем опускаются явные аргументы шаблона в угловых скобках. Однако такое сокращение работает только тогда, когда имя квалифицировано и указан класс или пространство имен, которые не содержат подходящей нешаблонной функции.
В частности, если пространство имен содержит (или получит позже) подходящую нешаблонную функцию, то будет выбрана именно она, поскольку се наличие означает, что реализуется случай 2, а не 3. Достаточно тонко и неожиданно, не правда ли? Здесь очень легко допустить ошибку, так что лучше избегать использования таких тонкостей.
Причина 2: удивляет программистов
Случай 3 оказывается неожиданным и удивительным для программистов, которые пытаются разобраться в коде и понять, как он работает. Рассмотрим, например, весьма незначительно отличающийся вариант кода - все отличие состоит в том, что я убрал квалифицирующую часть boost::.
имя стало неквалифицированным, а это означает нечто
совершенно иное
class Test {
-TestО { }
friend void checked delete( Test* x );
Если вы опустите boost:: (т.е. сделаете вызов неквалифицированным), то оказывается, что эта ситуация соответствует совсем другому случаю, а именно - случаю 4, который вообще не работает для шаблонов. Держу пари, что любой программист
согласится со мной, что такое кардинальное изменение смысла объявления друга из-за опускания имени пространства имен, по меньшей мере, неожиданно. Таких конструкций следует избегать.
Причина 3: удивляет компиляторы
Случай 3 оказывается неожиданным и удивительным для компиляторов, он может оказаться неприменимым па практике, даже если мы проигнорируем приведенные ранее причины отказаться от данного способа.
Испытаем коды, относящиеся к случаям I и 3, на разных компиляторах, и проанализируем результат. Воспринимают ли компиляторы стандарт так же, как и мы (дочитавшие книгу до этого места)? Будут ли, по крайней мере, самые мощные компиляторы вести себя так, как мы от них ожидаем? Оба ответа отрицательны...
Сначала обратимся к случаю 3.
пример 8-1 - еще раз
namespace boost {
tempiate lt;typename т gt; void checked delete( T* x ) { ... остальной код ... delete x;
class Test { -TestO { } изначальный код
friend void boost::checked deleteC Test* x );
i nt mainO {
boost::checked delete( new Test );
Попробуйте скомпилировать этот код на вашем компиляторе, и мы сравним наши результаты (см. табл. 8.1).
Таблица 8.1. Результат компиляции примера 8-1 разными компиляторами
Компилятор | Результат | Сообщение об ошибке |
Borland 5.5 | ||
Comeau 4.3,0.1 | ||
Digital Mars 8.38 | Error | Symbol Undefined ?checkcd.....dclete@@YAXPAV- Test@@@Z (void cdecl checked.....dclctc(Tc5t *)) |
EDG 3,0.1 | ||
Intel 6.0.1 | ||
gcc 2.95.3 | Error | ~boost::checked delete(Test *) should have been declared inside boost |
gcc .3.4 | Error | void boost::checked delete(Test*) should have been declared inside boost |
Metrowerks 8.2 | Error | friend void boost;:checked delete( Test* x ); name has not been declared in namespace/class |
MS VC++ 6.0 | Error | nonexistent function boost::checked deiete specified as friend |
MS VC++ 7.0 (2002) |
| 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 86 87 88 89 90 |