|
Программирование >> Полиморфизм без виртуальных функций в с++
Во время первоначального проектирования мы нашли множество таких примеров, а также обнаружили, что особые случаи зачастую чрезвычайно важны. Благодаря им удастся придать сдииообразие языку и повысить производительность. Строки в стиле С дают прекрасный пример. Поэтому я пришел к выводу, что нужен механиз.м для специализации ншбло-нов. Это можно было сделать, либо приняв обшие правила перегрузки, либо с помощью специального подхода. Я выбрал последнее, так как думал, что решаю в основном проблему нерегулярности, берущую свое начало в С, а также потому, что предложение о перегрузке неизменно встречает массу протестов. В первоначальном проекте специализация была определена как ограниченная и аномальная фор.ма перегрузки и плохо увязывалась с остальными частями языка. П1аблон класса или функции можно специализировать. Например, если есть шаблон template<class Т> class vector { . . . Т& operator[](int i); то .можно ввести специализации, то есть отдельные объявления, скажем, для vector<char> или vector<complex>::operator[] (int): class vector<char> { ... char& operator[](int i); complex& vector<complex>::operator[](int i) {/*...*/ } Это позволяет программисту вводить специализированные реализации для классов, которые либо особенно важны с точки зрения производительности, либо имеют отличающуюся от стандартной семантику. Грубый, но очень эффективный меха1шзм. Исходная .моя идея состояла в то.м, чтобы поместить такие специализации в библиотеку и авто.матически вызывать их по мере необходимости без вмешательства программиста. Оказалось, что цена такой услуги велика, а ценность сомнительна. Специализация приводила к трудностям для понимания и реализации, так как заранее было неизвестно, что будет подставлено для конкретного набора аргументов шаблона, - даже если у нас перед глаза.ми и.мелось его определение, - ибо этот шаблон .мог быть специализирован в другой единице трансляции. Например: template<class Т> class X { Т V; public: Т read О const ( return v; ) void write(int w) ( v = vv; } void f(X<int> г) { r.write(2) ; int i = r.read(); Кажется естественным предположить, что f () использует определенную выше функцию-член. Но это не гарантируется. В какой-то другой единице трансляции могла бы быть определена функция X<int>: : write (), которая делает нечто совершенно иное. Специализацию можно считать дырой в системе защиты С-ы-, так как специализированная функция-член может получать доступ к закрытым данным шаблона класса таким способом, который невозможно идентифицировать, просто читая определение шаблона. Были и другие технические проблемы. Я счел, что специализация в своем первоначальном виде реализована неудачно, но при этом предоставляет существенную функциональность. Как же можно сохранить эту функциональность, устранив все недостатки? После многих сложных рассуждений я предложил очень простое решение, которое было одобрено на заседании в Сан-Хосе: специализация должна быть объявлена перед использованием. Это ставит ее в один ряд с правилами обычной перегрузки. Если в области действия, где специализация используется, не видно ее объявления, то применяется обычное определение шаблона. Например: template<class Т> void sort(vector<T>& v) { /* ... */ } void sort<char*>(vector<char*>& v); специализация void f(vector<char*>& vl, vector<String>& v2) { sort(vl); используется специализация sort (vector<char*>5c) sort(v2); используется общий шаблон sort(vector<T>&), где Т - это String void sort<String>(vector<String>& v); ошибка: специализация после использования void sorto(vector<double>& v); правильно: sort(double) еще не использовалась Мы думали о явном ключевом слове для обозначения специализации. Например: specialise void sort(vector<String>&); но ко.митет на заседании в Сан-Хосе был настроен решительно против новых ключевых слов. К то.му же на этом собрании, где присутствовали люди разных национальностей, мы никогда не смогли бы прийти к единому мнению о том, как надо правильно писать: specialise или specialize. 15.10.4. Нахождение определений шаблонов Традиционно программа на С++, как и на С, представляет собой множество файлов, которые собираются в единицы трансляции, компилируются и связываются с помощью набора различных програ.мм, работающих на основе общих соглашений. Например, файлы с расширением . с - это исходные тексты; они включают . h-файлы для получения информации о других частях программы. Из . с-файлов компилятор генерирует объектные файлы, обычно имеющие расширение . о. Исполняемая программа получается путем связывания всех . о-файлов. Архивы и динамические подключаемые библиотеки несколько усложняют дело, но не из-.меняют картины в целом. Шаблоны не очень хорошо укладываются в описанную схему. Отсюда множество проблем, связанных с их реализацией. Шаблон - не просто исходный код (скорее, .можно назвать исходным кодом то, что получается в результате инстанцирования шаблона), поэтому определениям шаблонов не место в .с-файлах. С другой стороны, щаблоны не являются и просто типами или определениями интерфейсов, так что в . h-файлы их тоже помещать не стоит. На этот счет в ARM не было однозначных указаний разработчикам (см. раздел 15.10), в результате появилось множество разных схем, которые препятствовали переносимости. Для некоторых компиляторов требовалось, чтобы шаблоны находились в . h-файлах. Это может негативно отразиться на производительности, поскольку в каждую единицу трансляции входит слишком много информации и каждая единица становится зависимой от всех шаблонов, входящих в ее . h-файлы. Вообще-то шаблоны не являются частью заголовочных файлов. Другие компиляторы требуют, чтобы шаблоны находились в . с-файлах. Это усложняет нахождение определения шаблона функции, когда его надо инстанцировать, а также синтез контекста для инстанцирования. Видимо, любое решение указанных проблем должно основываться на признании того факта, что С++-программа - не просто (и не только) набор не связанных между собой единиц трансляции. Это верно даже на этапе компиляции. Каким-то образом нужно сформулировать концепцию центральной точки, в которой доступна информация о шаблонах и других элементах, относящихся сразу к нескольким единицам трансляции. Пока назовем эту точку репозитарием, поскольку ее основное назначение - хранить информацию, необходимую компилятору между трансляциями отдельных частей программы. Можно представлять себе репозитарий как хранящуюся на диске таблицу символов, в которой для каждого шаблона есть один элемент. Компилятор использует ее для получения информации об объявлениях, определениях, специализациях, использовании и т.д. С учетом такой концепции можно охарактеризовать .модель инстанцирования следующим образом: поддерживает все языковые средства, согласуется с применяемыми принципами использования .h и .с-файлов, не требует от пользователя знаний о репозитарий и предоставляет возможности для проверки ошибок, оптимизации и повышения эффективности компиляции
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |