Динамо-машины  Метод Сократа 

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