Анимация
JavaScript


Главная  Библионтека 

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

Задача 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