Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 86
Текст из файла (страница 86)
<...> Высказывалось мнение, что читать и понимать параметризованные типы будет проще, если задан полный набор Ограничения на аргументы шаблонов ПИИИИИКИ операций над типами параметров. Я вижу здесь две проблемы: списки операций могут оказаться очень длинными, а для многих приложений понадобится обьяшнпь больше шаблонов, чем зто реально необходимо». Видимо, я недооценил важность ограничений для удобства чтения и раннего выявления ошибок, ио, с другой стороны, обнаружились и дополиительиые проблемы при выражении таковых: тип функции специфичен, чтобы быть эффективиым ограничением.
Если рассматривать тип функции «буквально», то ои слишком ограничивает решение. В примере с шаблоном класса чес сот логичио ожидать, что подходит любая операция <, принимающая два аргумента типа т. Однако кроме встроенного оператора < есть еще несколько разумных альтернатив: ьпс Х::оретасот<(Х); тпс у::оретасот<(сопят уь)г 1пС оретатот<(Е,Е); 1пс оретасот<(сопят ЕЕа, сопят ЕЕь); При буквальной интерпретации допустимым аргументом для чессот является только вариант с ее. Возможиость наложить ограничения иа шаблоны обдумывалась неоднократно: о некоторые пользователи полагают, что при наличии ограничений иа аргументы шаблонов можно сгенерировать оптимальный код, — я в это ие верю; и другие думают, что в отсутствие ограничений ставится под угрозу статическая проверка типов.
На самом деле только некоторые этапы проверки откладываются до момента компоновки и иа практике это является проблемой; о кто-то считает, что при наличии ограничений объявление шаблона было бы проще понять. Часто это действительно так.
В следующих пунктах описываются два способа выражения ограничения. В некоторых случаях альтернативами ограничениям являются генерирование функций-членов, когда оии действительно нужны (см. рзздел 15.5), и специализация (см. раздел 15.10.3). 15А.'(. Ограничения эа счет наследования Дуг Леа, Эндрю Кениг, Филипп Готрои (РЬ(1(рре бац(гоп), я и многие другие независимо обнаружили, как можно воспользоваться синтаксисом наследования для выражения ограничений.
Например: Сепр1аСе <с1авв Т> с1авв СопратаЫе ( ть оретасот=(сопят та); тпс оретасот==(сопвс та, сопвс ть)г тпс оретасот<=(сопвс та, сопвс та); (пс оретасот<(сопвс та, сопвс та); Сепр1ате <с1авв Т : Сопратаьте> с1авв тестот ( l/ )' Шаблоны БИИИИИЮИ Это имеет смысл.
Предложения отличаются деталями, но позволяют перенести обнаружение ошибок и диагностику на этап компиляции отдельной единой трансляции. Возможные варианты пока обсуждаются в группе по стандартизации С++. У меня, впрочем, есть принципиальные возражения против выражения ограничений с помощью наследования. Данная возможность будет подталкивать программистов к такой организации программ когда все то, что может являться разумным ограничением, выносится в класс, а наследование рассматривается как средство выражения любых ограничений.
Например, вместо того чтобы сказать «в классе т должен быть оператор меньше», придется говорить «класс т должен быть производным от класса соврагаЫе». Это непрямой и негибкий способ выражения ограничений, который легко ведет к злоупотреблению наследованием. Поскольку между встроенными типами (фпс, доцЬ1е и т.д.) нет отношений наследования, то использовать его для выражения ограничений на такие типы нельзя. Недопустимо также применение наследования для выражения ограничений„которые можно использовать одновременно со встроенным и определенным пользователем типами.
Например, с помощью наследования нельзя сказать, что 1пс и солзр1ех допустимы в качестве аргументов шаблона. Далее, автор шаблона не может предвидеть всех возможных применений средства. Поэтому сначала на аргументы шаблона будут накладываться чересчур строгие ограничения, а затем — на основе опыта — они начнут излишне ослабляться. Логическим следствием использования метода «ограничений за счет наследования» будет введение универсального базового класса, выражающего идею «отсутствия ограничений».
Но наличие подобного базового класса породит небрежное отношение к программированию как в контексте использования шаблонов, так и в других ситуациях (см. раздел 14.2.3). Применение наследования в качестве средства ограничений на аргументы шаблонов не позволяет программисту написать два шаблона с одним и тем же именем, один из которых используется для указательных, а другой — для неуказательных типов. Проблему — мое внимание к ней привлек Кит Гордец — можно решить с помощью перегрузки шаблонов функций (см. раздел 15.6.3.1).
Фундаментальное основание для критики этого подхода: наследование используется для того, что не является порождением подтипов. По-моему, наследование применяется для выражения ограничений не в силу глубоких внутренних причин, Отношения наследования — не единственные полезные отношения, имеющиеся в языке.
15.4.2. Ограничения за счет использования Когда я впервые работал с компилятором, реализующим шаблоны, то решил проблему ограничений, выразив их в виде встраиваемой функции. Например: сеюр1асе<с1аав Т> с1авв Х ( // чей сопасгатпсз(т* ср( ( // т должен иметь: // доступный базовый класс В // функцию-член й Устранение дублирования кода ИИИИИИИП т а(1); // конструктор из гпс а = *ср; // оператор присваивания // ) ): сеюр1асе<с1азз т> сопвсгаспсз т* )зр; в* )зр = ср; ср->г()/ т а(1); а = *ср; // // т должен иметь: // доступный базовый класс В Функцию-член с // конструктор из 1пс // оператор присваивания ) с1азз Х ( // ); Формализация позволила бы налагать ограничения и на аргументы шаблонов функций, но вряд ли такой подход стоит реализовывать.
Однако из всех известных мне систем вта — единственная приближающаяся к моему принципу не вводить слишком жесткие ограничения на аргументы шаблонов, сохраняя в то же время единство, лаконичность, доступность и простоту реализации. Примеры важных ограничений, выраженных функциями-членами шаблонов, см. в разлелах 15.9.1 и 15.9.2. 155. Устранение дублирования кода Устранение ненужного расхода памяти, вызванного слишком большим числом инстанцирований, считалось первоочередной проблемой проектирования языка, а не деталью реализации. Правила, требующие позднего инстанцирования функций-членов шаблона сом. разделах 15.10 и 15.10.4), гарантируют, что код не будет К сожалению, здесь используется одна деталь, характерная для определенного компилятора: С1гопс выполняет полный синтаксический и семантический контроль всех встраиваемых функций в момент инстанцирования объявленного шаблона.
Однако вариант функции с конкретным набором аргументов не следует инстанцировать, если она фактически не вызывается сом. раздел 15.5). Такой подход позволяет автору шаблона задать ограничивающую функцию, а пользователь может проверить выполнение ограничения, вызвав ее в удобный для себя момент. С другой стороны, автор может вызывать функцию сопвсгаспсв ( ) и сам из каждого конструктора. Но это утомительно, если конструкторов много и не все из них встраиваются. Эту концепцию можно было бы формализовать, введя в язык специальное средство: Шаблоны ПИИИИИИИ дублироваться, если шаблон используется с одними и теми же аргументами в разных единицах трансляции. Я считал маловероятным, что при раннем [или даже позднем) инстанцировании шаблона возможно будет поискать, не инстанцирован ли тот же шаблон с другими аргументами, и определить, когда можно разделять инстанцированный код полностью или частично.
И все же было чрезвычайно важно избежать такого неоправданного увеличения кода, которое встречается при расширении макросов и в языках с примитивным механизмом инстанцирования [8(гоизггпр, 1988о]: «Среди прочего наотедование гарантирует разделенна кода между различными типами (код нввиртуального базового класса разделяется всеми производными от него классами]. Экземпляры июблона не разделяют код если только не применяется какая-либо своеобразная техника компиляции. Я не питаю надежд на скорое появление такой техники.
Но можно ли воспользоваться наследованием для решения проблемы дублирования кода, возникающвй нз-за применения шаблоновт Для этого потребовалось производить шаблон от обычного класса. Например: // обобщенный тип вектора тещр1ате<с1авв Т> с1авв честит ( * ч тпт вз! риЫ1с: честит(!пт); та е1ещ(1пт 1) ( тетигп ч[1]; та орегатот[]([пт 1)г // ) тещр1ате<с1авв т> с1авв рчестот : честог<чо[б*> ( // вектор указателей // производный от честог<чоьб*> риЫ1с: рчестог(тпт 1) : честот<чо10*>(1) () т*а е1ещ(1пт 1) ( тетигп (Т*а) честит<то!0*>::е1ещ(1); т*а орегатот[)(!пт 1) [ тетигп (т*а) честог<чо[г)*>::оретатог[](1); ) // рчестог<1пт*> р1чес(100); рчестот<сощр1ех*> Тсщрчес(200)г рчестог<с)тат*> рсчес(300); Реализации всех трех классов вектора указотвлвй полностью разделяются.
Все ахеи иописа. ны исключительно с помощью иагледования и встраивания на основе класса честит<ус!<) '>. Реализация жв ч ес тот тот 0 < > — один из серьезных кандидатов на включение в стандартную библиотеку». Благодаря описанной технике удалось предотвратить неоправданное увеличение объема кода. Те, кто не пользовался чем-то подобным [в С++ или в других языках со сходными средствами параметризации типов), сталкивались с неприятным Шаблоны функций ИИИИИИИИИ =юрпризом: на дублированный код могли уходить мегабайты памяти даже в программе скромного размера.
С другой стороны, я считал важным, чтобы компилятор инстанцировал лишь ге функции-члены шаблона, которые использовались реально. Например, если есть шаблон т с функциями г и 9, то компилятор должен инстанцировать только 1, если д лля данных аргументов шаблона не вызывается. К тому же если вариант функции-члена будет генерироваться при данном наборе аргументов шаблона, только когда вта функция действительно вызывается, то мы повысим гибкость программы [8сгоцьсгцр, 1988о): «Рассмотрим че с с от <т>. Еоги мы хотим реализовать опв рацию сортировки, необходимо потрвбовать, чтобы для типа т было опрвдвлвно некоторое отношвнив порядка.
Ток обстоит дало нв для всех типов. 6сли бы набор операций над т нужно было задавать в объявлении нес сот, пришлось бы имвть два типа ввкторов: один для объектов, имеющих отношение порядка, другой — для остальных. 6аги жв множество операций над Т задавать необязательно, достаточно и одного типа. Разум«атея, нельзя будет сортировать векторы из обьвктов типа п1 оь, для которых отношвнив порядка нв опрвдвлвно.