|
Программирование >> Полиморфизм без виртуальных функций в с++
операций над типами параметров. Я вижу здесь две проблемы: списки операций могут оказаться очень длинными, а для многих приложений понадобится объявлять больше шаблонов, чем это реально необходимо . Видимо, я недооценил важность ограничений для удобства чтения и раннего выявления ошибок, но, с другой стороны, обнаружились и дополнительные проблемы при выражении таковых: тип функции специфичен, чтобы быть эффективным ограничением. Если рассматривать тип функции буквально , то он слишком ограничивает решение. В примере с шаблоном класса vector логично ожидать, что подходит любая операция <, принимающая два аргумента типа Т. Однако кроме встроенного оператора < есть еще несколько разумных альтернатив: int X::operatOr<(Х) ; int Y::operator<(const Y&); int operator<{Z,Z); int operator<(const ZZ&, const ZZ&); При буквальной интерпретации допустимым аргументом для vector является только вариант с ZZ. Возможность наложить ограничения на шаблоны обдумывалась неоднократно: □ некоторые пользователи полагают, что при наличии ограничений на аргументы шаблонов можно сгенерировать оптимальный код, - я в это не верю; □ другие думают, что в отсутствие ограничений ставится под угрозу статическая проверка типов. На самом деле только некоторые этапы проверки откладываются до момента компоновки и на практике это является проблемой; □ кто-то считает, что при наличии ограничений объявление шаблона было бы проше понять. Часто это действительно так. В следующих пунктах описываются два способа выражения ограничения. В некоторых случаях альтернатива.ми ограничениям являются генерирование функций-членов, когда они действительно нужны (см. раздел 15.5), и специализация (см. раздел 15.10.3). 75.4.7. Ограничения за счет наследования Дуг Леа, Эндрю Кениг, Филипп Готрон (Philippe Gautron), я и многие другие независимо обнаружили, как можно воспользоваться синтаксисом наследования для выражения ограничений. Например: template <class Т> class Comparable { Т& operator=(const Т&); int operator==(const T&, const T&); int operator<=(const T&, const T&); int operator<(const T&, const T&); template <class T : Comparable> class vector { . . . Это имеет смысл. Предложения отличаются деталями, но позволяют перенести обнаружение ошибок и диагностику на этап компиляции отдельной единой трансляции. Возможные варианты пока обсуждаются в группе по стандартизации С++. У меня, впрочем, есть принципиальные возражения против выражения ограничений с помощью наследования. Данная возможность будет подталкивать программистов к такой организации программ когда все то, что может являться разумны.м ограничением, выносится в класс, а наследование рассматривается как средство выражения любых ограничений. Например, вместо того чтобы сказать в классе т должен быть оператор меньше , придется говорить класс Т должен быть производным от класса СотрагаЫе . Это непрямой и негибкий способ выражения ограничений, который легко ведет к злоупотреблению наследованием. Поскольку между встроенными типами (int, double и т.д.) нет отношений наследования, то использовать его для выражения ограничений на такие типы нельзя. Недопустимо также применение наследования для выражения ограничений, которые можно использовать одновременно со встроенным и определенным пользователе.м типами. Например, с помощью наследования нельзя сказать, что int и complex допустимы в качестве аргументов шаблона. Далее, автор шаблона не может предвидеть всех возможных применений средства. Поэтому сначала на аргументы шаблона будут накладываться чересчур строгие ограничения, а затем - на основе опыта - они начнут излишне ослабляться. Логическим следствием использования метода ограничений за счет наследования будет введение универсального базового класса, выражающего идею отсутствия ограничений . Но наличие подобного базового класса породит небрежное отношение к программированию как в контексте использования шаблонов, так и в других ситуациях (см. раздел 14.2.3). При.мепение наследования в качестве средства ограничений на аргументы шаблонов не позволяет программисту написать два шаблона с одним и тем же именем, один из которых используется для указательных, а другой - для неуказательных типов. Проблему - мое внимание к ней привлек Кит Горлен - можно решить с по.мощью перегрузки шаблонов функций (см. раздел 15.6.3.1). Фундаментальное основание для критики этого подхода: наследование используется для того, что не является порождением подтипов. По-моему, наследование применяется для выражения ограничений не в силу глубоких внутренних причин. Отношения наследования - не единственные полезные отношения, имеющиеся в языке. 15.4.2. Ограничения за счет использования Когда я впервые работал с компилятором, реализующим шаблоны, то решил проблему ограничений, выразив их в виде встраиваемой функции. Например: template<class Т> class X { . . . void constraints{Т* tp) { Т должен иметь: В* bp = tp; доступный базовый класс В tp->f{); функцию-член f Т а(1); конструктор из int а = *tp; оператор присваивания . . . К сожалению, здесь используется одна деталь, характерная для определенного компилятора: Cfront выполняет полный синтаксический и семантический контроль всех встраиваемых функций в момент инстанцирования объявленного шаблона. Однако вариант функции с конкретны.м набором аргументов не следует инстанцировать, если она фактически не вызывается (см. раздел 15.5). Такой подход позволяет автору шаблона задать ограничивающую функцию, а пользователь .может проверить выполнение ограничения, вызвав ее в удобный для себя момент. С другой стороны, автор может вызывать функцию constraints () и сам из каждого конструктора. Но это утомительно, если конструкторов много и не все из них встраиваются. Эту концепцию можно было бы фор.мализовать, введя в язык специальное средство: template<class Т> { constraints { Т* bp; Т должен иметь: В* bp = tp; доступный базовый класс В tp->f(); функцию-член f Т а(1); конструктор из int а = *tp; оператор присваивания . . . class X { ... Фор.мализация позволила бы налагать ограничения и на аргументы шаблонов функций, но вряд ли такой подход стоит реализовывать. Однако из всех известных мне систе.м эта - единственная приближающаяся к моему принципу не вводить слишком жесткие ограничения на аргументы шаблонов, сохраняя в то же вре.мя единство, лаконичность, доступность и простоту реализации. Примеры важных ограничений, выраженных функциями-членами шаблонов, см. в разделах 15.9.1 и 15.9.2. 15.5. Устранение дублирования кода Устранение ненужного расхода 1ш.мяти, вызванного слишком большим числом инстанцирований, считалось первоочередной проблемой проектирования языка, а не деталью реализации. Правила, требующие позднего инстанцирования функций-членов шаблона (см. разделах 15.10 и 15.10.4), гарантируют, что код не будет
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |