Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 93
Текст из файла (страница 93)
П)аблон класса или функции можно специализировать. Например, если есть шаблон Шаблоны НИИИИИИИВ чоЫ Г(Х<(пс> г) ( г.нггге(2); Ыг 1 = г.ген(); ) Кажется естественным предположить, что б () использует определенную выше функцию-член. Но это не гарантируется. В какой-то другой единице трансляции могла бы быть определена функция х<1пс>:: нгьсе ( ), которая делает нечто совершенно иное. Специализацию можно считать «дырой» в системе защиты С++, так как специализированная функция-член может получать доступ к закрытым данным шаблона класса таким способом, который невозможно идентифицировать, просто читая определение шаблона. Были и другие технические проблемы. Я счел, что специализация в своем первоначальном виде реализована неудачно, но при этом предоставляет существенную функциональность.
Как же можно сохранить эту функциональность, устранив все недостатки? После многих сложных рассуждений я предложил очень простое решение, которое было одобрено на заседании в Сан-Хосе: специализация должна быть объявлена перед использованием. Это ставит ес в один ряд с правилами обычной перегрузки. Если в области действия, где специализация используется, не видно се объявления, то применяется обычное определение шаблона. Например: Гешр1асе<с1авв Т> чоЫ вогс(чессог<Т>а ч) /* ... '/ чоЫ вогг<сйаг*>(чесгог<с)1аг*>ь ч); // специализация чозгЫ г (чессог<с)1аг*>В ч1, чессог<ясг1пд>а ч2) ( вогг(ч1); // используется специализация // вогг(чесгог<с)1аг'>а) вогс(ч2); // используется общий шаблон // вогс(чессог<Т>а), где Т - это Ясгвпд ) чоЫ вогг<зггтпд>(чесгог<згг1пд>а ч); // ошибка: специализация // после использования чоЫ вогг<>(чесгог<боиЫе>а ч); // правильно: вогс(допЬ1е) // еще не использовалась Мы думали о явном ключевом слове для обозначения специализации.
Например: вресга11ве чоы вогг (чесгог<яггыд>а) / но комитет на заседании в Сан-Хосе был настроен решительно против новых ключевых слов. К тому же на этом собрании, где присутствовали люди разных Инстанцирование шаблонов НИИИИИБН национальностей, мы никогда не смогли бы прийти к единому мнению о том, как надо правильно писать: эрес' а1ьзе или эресьа1ьзе. 15. 10.4.
Нахождение определений шаблонов Традиционно программа на С++, как и на С, представляет собой множество файлов, которые собираются в единицы трансляции, компилируются и связываются с помошью набора различных программ, работающих на основе общих соглашений. Например, файлы с расширением . с — это исходные тексты; они включают . 'и-файлы для получения информации о других частях программы. Из . с-файлов компилятор генерирует объектные файлы, обычно имеющие расширение . о. Исполняемая программа получается путем связывания всех . о-файлов. Архивы и динамические подключаемые библиотеки несколько усложняют дело, но не изменяют картины в целом. Шаблоны не очень хорошо укладываются в описанную схему. Отсюда множество проблем, связанных с их реализацией.
Шаблон — не просто исходный код (скорее, можно назвать исходным кодом то, что получается в результате инстанцирования шаблона), поэтому определениям шаблонов не место в . с-файлах. С другой стороны, шаблоны не являются и просто типами или определениями интерфейсов, так что в . Ь-файлы их тоже помешать не стоит. На этот счет в АКМ не было однозначных указаний разработчикам (см. раздел 15.10), в результате появилось множество разных схем, которые препятствовали переносимости.
Для некоторых компиляторов требовалось, чтобы шаблоны находились в . 11-файлах. Это может негативно отразиться на производительности, поскольку в каждую единицу трансляции входит слишком много информации и каждая единица становится зависимой от всех шаблонов, входящих в ее . В-файлы, Вообше-то шаблоны не являются частью заголовочных файлов. Другие компиляторы требуют, чтобы шаблоны находились в . с-файлах. Это усложняет нахождение определения шаблона функции, когда его надо инстанцировать, а также синтез контекста для инстанцирования.
Видимо, любое решение указанных проблем должно основываться на признании того факта, что С++-программа — не просто (и не только) набор не связанных между собой единиц трансляции. Это верно даже на этапе компиляции. Каким-то образом нужно сформулировать концепцию центральной точки, в которой доступна информация о шаблонах и других элементах, относящихся сразу к нескольким единицам трансляции. Пока назовем эту точку репозитарием, поскольку ее основное назначение — хранить информацию, необходимую компилятору между трансляциями отдельных частей программы. Можно представлять себе репозитарий как хранящуюся на диске таблицу символов, в которой для каждого шаблона есть один элемент.
Компилятор использует ее для получения информации об объявлениях, определениях, специализациях, использовании и т.д. С учетом такой концепции можно охарактеризовать модель инстанцирования следуюшим образом: поддерживает все языковые средства, согласуется с применяемыми принципами использования . Ь и . с-файлов, не требует от пользователя знаний о репозитарии и предоставляет возможности для проверки ошибок, оптимизации и повышения эффективности компиляции НИИИИИИ! 1 Шаблоны и компоновки, о которых просят разработчики компиляторов. Заметим, что это модель системы инстанцирования, а не правило языка и не характеристика конкретной реализации. Возможно несколько альтернативных реализаций, но я полагаю, что пользователь может игнорировать детали (почти всегда) и представлять себе систему так, как описано выше. Рассмотрим предполагаемую работу компилятора в различных ситуациях.
Как обычно, на вход компилятора подаются . с-файлы. Они содержат директивы М хпс1иде для включения . ц-файлов. Компилятору известно только о переданном ему коде. То есть в файловой системе никогда не ишется определение шаблона, которое не дано. Однако компилятор использует репозитарий, как бы запоминая, какие шаблоны уже известны и откуда они взялись. Эту схему легко расширить с учетом архивов. Вот краткое описание того, как происходит работа компилятора в некоторых ключевых точках: о распознано объявление шаблона. Теперыпаблон можно использовать.
Он помещается в репозитарий; о в . с-файле распознано определение шаблона функции. Шаблон обрабатывается с целью помещения в репозитарий. Если он уже находится там, появляется ошибка повторного определения за исключением случаев, когда это новая версия того же шаблона; о в . Ь-файле распознано определение шаблона функции. Шаблон обрабатывается с целью помещения в репозитарий. Если он уже там находится, то осушествляется проверка, был ли на самом деле ранее помещенный шаблон добавлен именно из этого заголовочного файла. Если это не так, диагностируем ошибку повторного определения. Проверяем, не нарушено ли правило одного определения.
Для этого надо убедиться, что старое определение совпадает с новым. В противном случае диагностируем ошибку повторного определения, если только это не новая версия того же шаблона; о распознано объявление специализации шаблона функции. Если необходимо, выдаем ошибку об использовании до объявления. Теперь специализацию можно использовать. Помещаем объявление в репозитарий; о распознано определение специализации шаблона функции. При необходимости выдаем ошибку об использовании до объявления. Теперь специализацию можно использовать.
Помешаем определение в репозитарий; о распознано использование. Заносим в репозитарий запись об использовании шаблона с данным набором аргументов. Смотрим, есть ли в репозитарии определение общего шаблона или его специализации. Если да, то допустимо выполнить контроль ошибок или оптимизацию.
Если шаблон еше не использовался с таким набором аргументов, то допустимо сгенерировать код сейчас или отложить это до этапа компоновки; о распознан запрос явного инстанцирования. Проверяем, определен ли шаблон. Если нет, выдаем сообщение о неопределенном шаблоне. Проверяем, определена ли специализация. Если да, выдаем сообшение чинстанцирован и специализированы Проверяем, был ли уже инстанцирован шаблон с данным набором аргументов. Если да, можно либо выдать сообщение о повторном Последствия введения шаблонов МИИИИИЕБ инстанцировании, либо игнорировать запрос.
В противном случае допустимо сгенерировать код сейчас или отложить это до этапа компоновки. В любом случае код генерируется для каждой функции-члена шаблона класса, которую видит компилятор; а программа компонуется. Сгенерировать код для каждого использованного шаблона, код которого не сгенерирован ранее. Повторить этот процесс, пока не будут обработаны все ипстанцирования. Выдать сообщение «использована, но не опрелеленаь для всех отсутствующих шаблонов функций. При генерации кода шаблона для данного набора аргументов используется алгоритм поиска, рассмотренный в разделе 15.10.2. Разумеется, должны выполняться проверки некорректного использования, недопустимой перегрузки и т.д.
В компилятор могут быть заложены более или менее жесткие требования к исполнениям правила олного определения и правила, запрещающего многократное инстанцирование. Такая диагностика не является обязательной, поэтому поведение в этих случаях следует считать лишь вопросом качества реализации. 15.11. Последствия введения шаблонов Отсутствие шаблонов в раннем С++ имело негативные последствия для непользования языка.
Теперь, когда шаблоны получили широкое распространение, какие задачи можно решать лучше? Из-за отсутствия шаблонов в С++ не было способа реализовать контейнерные классы, не прибегая к интенсивному использованию приведения типов и манипуляции объектами через указатели на общие базовые классы или чоЫ*. Теперь от всего этого можно отказаться. Но неправильное использование наследования, берущее начало в бездумном переносе в С++ методов из Зша!!гаП» (см., например, пункт 14.2.3) и злоупотребление слабой типизацией, заимствованной из С, выкорчевать будет очень трудно.