Анимация
JavaScript
|
Главная Библионтека typedef typename Se"lect<isPo"lymorphic, T*, T>::Resu"lt valueType; 2.7. Распознавание конвертируемости и наследования на этапе компиляции При реализации шаблонных функций и классов часто возникает следующий вопрос. Даны два произвольных типа т и и, о которых ничего не известно. Как определить, наследует ли класс и свойства класса т? Распознавание таких отношений является ключевой проблемой при оптимизации обобщенных библиотек. Если класс реализует определенный интерфейс, при работе с обобщенной функцией можно положиться на оптимизированный алгоритм. Такое распознавание на этапе компиляции позволяет избежать динамического приведения типов с помощью оператора dy-namic cast, на что обычно уходит много машинного времени. Распознавание наследования - более общий процесс, чем распознавание конвертируемости. Общая проблема формулируется следующим образом. Как узнать, возможно ли автоматическое преобразование произвольного типа т в произвольный тип и? Можно, например, использовать функцию si zeof. Ее мощь удивительна. Эту функцию можно применять к любому сколь угодно сложному выражению, и она вернет размер его результата, не прибегая к вычислениям в ходе выполнения программы. Это означает, что функция si zeof распознает перегрузку, конкретизацию шаблонов, правила преобразования типов - все, что относится к выражениям в языке C-t-1-. Фактически функция si zeof игнорирует само выражение, возвращая лишь размер его результата. Идея, на которой основан механизм распознавания конвертируемости, состоит в использовании функции si zeof в сочетании с перегруженными функциями. Для функции предусматриваются две перефузки. Одна из них получает тип, который должен конвертироваться (в тип и), а вторая - что-нибудь другое (неважно, что именно). Будем вызывать перефуженную функцию с временной переменной типа т, о котором нужно узнать, конвертируется ли он в тип и. Если будет вызвана функция, получающая параметр типа и, значит, тип т конвертируется в тип и. Если будет вызвана нейтральная функция, то тип т не конвертируется в тип и. Для того чтобы определить, какая функция была вызвана, нужно предусмотреть, чтобы две перегруженные функции возвращали значения, типы которых имеют разные размеры. Для определения этих размеров следует вызвать функцию si zeof. Сами по себе типы нас не интересуют - лишь бы они имели разные размеры. Итак, сначала создадим два типа с разными размерами. (Очевидно, что типы char и "long double имеют разные размеры, однако это не гарантируется стандартом.) Безопасная схема выглядит следующим образом. Есть предложение включить в язык С++ оператор typeof, т.е. оператор, возврашаюший тип выражения. С его помошью намного проще писать и понимать шаблонные коды. Язык Gnu С++ уже реализовал оператор typeof в качестве своего расширения. Очевидно, что оператор typeof и функция si zeof используют один и тот же код, поскольку в любом случае функция si zeof должна распознавать тип. typedef char Small; class Big { char dummy[2]; }; По определению значение sizeof (Smal 1) равно 1. Размер типа Big неизвестен, но он определенно больше, чем 1 (гарантировать можно только это). Затем нам понадобятся две перегрузки. Одна из них должна получать параметр типа и, а возвращать - значение типа Small, например: small Test(U); Как написать функцию, получающую "что-нибудь другое"? Шаблон не подходит, поскольку он всегда квалифицируется как наилучшее соответствие, а значит, скрывает преобразование типов. В данном случае нам нужно "плохое" соответствие, т.е. преобразование, которое происходит, только если автоматическое преобразование невозможно. Бегло просмотрев правила преобразования, применяемые к функции, легко убедиться, что наихудшим является соответствие, порождаемое эллипсисом (ellipsis match), которое находится в самом конце списка. Big Teste . •) ; (Передача объекта в функцию, описание которой содержит эллипсис, может привести к непредсказуемым результатам, но это не имеет значения. На самом деле функция не вызывается. Она даже не реализуется. Напомним, что функция sizeof не вычисляет свой аргумент.) Теперь нам необходимо применить функцию sizeof к вызову функции Test, передав ей объект типа т. const bool convExists = sizeof(Test(T())) == sizeof(Smal1); Вот и все! При вызове функция Test получает объект, созданный по умолчанию, - т(), а затем функция sizeof вычисляет размер результата этого выражения. Это может быть либо число sizeof (Big), либо число sizeof (Small), в зависимости от того, нашел компилятор преобразование или нет. Осталась нерешенной одна небольшая проблема. Если объект класса т создается закрытым конструктором, предусмотренным по умолчанию, то выражение т() порождает ошибку во время компиляции, что разрушает всю нашу стройную конструкцию. К счастью, эта проблема имеет простое решение. Достаточно использовать фиктивную функцию, возвращающую объект класса т. (Еще раз напомним, что чудесная функция sizeof на самом деле не вычисляет выражения.) Тогда и компилятор будет сыт, и профамма цела. т МакетО ; не реализуется const bool convExists = sizeof(Test(MakeT())) == sizeof(Small); (Кстати, обратите внимание, как остроумно устроена эта проверка - функции Макет и Test не только не имеют никаких параметров, но и не существуют вообще!) Итак, все детали этого механизма работают хорошо, и настало время упаковать их в шаблонный класс, который скрывает подробности, связанные с процессом вывода классов, оставляя на поверхности лишь окончательный результат. template <class т, class U> class Conversion typedef char Small; class Big { char dummy[2]; }; static small Test(U); static Big Test(...); static т макете); public: enum { exists = sizeof(Test(MakeT())) == sizeof(small) }; Теперь можно испытать шаблонный класс Conversion. int main О { using namespace std; cout « Conversion<double, int>::exists « « Conversion<char, char*>::exists « « Convesrion<size t, vector<int> >::exists « ; Эта небольшая программа выводит на печать строку "1 О О". Обратите внимание на то, что, хотя класс std::vector реализует конструктор, получаюший параметр типа size t, программа возврашает число О, поскольку это явный конструктор. В классе можно реализовать еше одну константу Conversion::sameType, прини-маюшую значение true, если классы т и и представляют собой один и тот же тип. template <class т, class и> class Conversion ... как и раньше ... enum { sameType = false }; Реализуем константу sameType с помошью частичной специализации шаблонного класса Conversion. template <class т> class Conversion<T, Т> { public: enum { exists = 1, sameType = 1 }; Итак, вернемся к исходной задаче. С помошью класса Conversion легко обнаружить наследование. #define supersubclass(t, и) \ (Conversion<const и*, const т*>::exists && \ !Conversion<const т*, const void*>::sameType); Макрос SUPERSUBCLASS(T, u) вычисляет значение true, если класс и является производным от класса т, или если классы т и и представляют собой один и тот же тип. Этот макрос выполняет свою работу, распознавая конвертируемость указателей типа const и* в указатели const Т*. Возможны только три случая, в которых указатели типа const и* неявно преобразовываются в указатели const т*. 1. Классы т и и представляют собой один и тот же тип. 2. Класс т является единственным открытым базовым классом для класса и. 3. Класс т представляет собой тип void. Последнее исключается при второй проверке. На практике бывает полезно считать первый вариант (классы т и и представляют собой один и тот же тип) вырожденным случаем отношения "является" ("is-a"), поскольку с практической точки зрения лю- 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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |