Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 89
Текст из файла (страница 89)
Этого можно достичь следующим образом: !етр(а!в<с(авв Т> (етргазе<сгазв Т2> Рп<Т>::орега!ог Ргг<Т2> () (ге!ига Р!г<Т2> (р); ) Здесь оператор ге(цгп будет компилироваться тогда и только тогда, когда указатель р (имеет тип Т ) может быть аргументом конструктора Рп<72> (72*) . Поэтому, если Т" может неявно приводиться к П*, то преобразование Рп<Т> в Рп<72> будет работать. Например; иоЫ Т(ргг<С! с1е> рс) ( Р(с<опаре> рв = рс; Рве<С!гоге> рс2 = рз; ) 7!о!с можно привести Сис(е* к опаре'" У еггогг нельзя привести Бпаре* к С!гс!е* Старайтесь определять лишь логически осмысленные операции преобразования.
Обратите внимание на то, что списки параметров самого шаблона и его шаблонных членов объединять нельзя. Например: ветр!а!в<с!ат Т, с(азз Т2> !7 еггог Ргг<Т>::орегагог Р1г<Т2> () (гешгп Ри<Т2> (р); ) 13.7. Организация исходного кода Существует два очевидных способа организации кода, использующего шаблоны: 1. Включать определения шаблонов до их использования в той же самой единице трансляции.
Мы хотим определить операции преобразования для пользовательского типа Рп так, чтобы операции преобразования между объектами этого типа были такими же, как между встроенными указателями при наследовании. Например: Я28 Глава 13. Шаблоны 2. Включать лишь объявления шаблонов до их использования в единице трансляции, а определения компилировать отдельно. Кроме того, шаблонные функции могут сначала объявляться, затем использоваться и лишь после этого определяться в одной и той же единице трансляции. Для иллюстрации различий между двумя подходами рассмотрим простой шаблон: Иис!и«е <<озиеат> (етр(а(е<с!аез Т> гоЫ ои( <соиз< Ть й ) зЫ:: сег «(( ) Мы могли бы поместить этот фрагмент в файл ои(. с, и включать его директивой 41ис1и<1е в те места, где вызывается ои(<).
Например: У изег!.с: ))<ис!и<(е "ои(.с" <7 исиользуем ои<Г) У изегдс: ))<ис!и«е "ош. с" Ф исиользуем ои(() Таким образом, определение функции ои(<) и все объявления, от которых она зависит, должны помешаться в каждую единицу трансляции, где ои([) используется. Далее уже от компилятора зависит, как именно он будет бороться с избыточными определениями и устранять эту избыточность (по возможности).
Такая стратегия трактует шаблонные функции так же, как встраиваемые функции. Ее недостатком является излишняя нагрузка на компилятор, который должен переваривать большие объемы информации. Другая проблема состоит в том, что пользователи могут случайно получить ненужные дополнительные зависимости от объявлений, необходимых лишь определению функционального шаблона ои() ) . Опасность этой проблемы можно минимизировать, используя пространства имен, избегая макросов и, вообще, минимизируя количество дополнительно включаемой информации. Другим подходом является стратегия раздельной компиляции, которая вытекает из следующего рассуждения: если определение шаблона не включается в код пользователя, то никакие нужные шаблону объявления не влияют на пользовательский код.
Таким образом, мы разбиваем наш файл оивс на два файла; У ои<.рс <етр(а(е<с(ат Т> гоЫ ои( <сои(< Ть () гу ои(.с: ))<ис1и«с<<аз( еат> Иис<и«е "ои<. ((" ехрог( <етр<а(е< с<аз( Т> гоЫ ои( < сове( Ть <) ) зЫ:: сегг « (( ) Теперь файл ои(.с содержит всю информацию, необходимую для определения ои((), а файл ои(.1( содержит лишь информацию, необходимую для вызова ои(<) .
При этом подходе в пользовательский код включается только объявление шаблона (интерфейс): 429 13.7 Организация исходного кода П изег!.с: №!ис!идс "ои!. й" )используем оиг() У изег2.с: №!ис!идс "ои!. й" П используем оигО Эта стратегия трактует шаблонные функции как обычные невстраиваемые функции. Определение (в файле оиг. с) компилируется отдельно, и задачей реализации является найти его при необходимости. Это также нагружает реализацию, но по-другому, чем в случае борьбы с избыточными определениями. Отметим, что определение (или объявление) шаблона должно быть дано с ключевым словом ехрогг (59.2.3)', или оно будет недоступно из клиентского кода.
В противном случае оно должно находиться в той области видимости, где оно используется. Какая стратегия (или их комбинация) является оптимальной, зависит от используемых компилятора и компоновщика, от внутренних особенностей разрабатываемой программной системы, и от внешних ограничений, накладываемых на нее. Обычно, встраиваемые функции и небольшие шаблонные функции, которые главным образом вызывают другие шаблонные функции, являются кандидатами на включение в каждую единицу трансляции, в которой они используются. Для реализаций с умеренной поддержкой компоновщика в вопросе конкретизации шаблонов такой подход ускоряет процесс компиляции и повышает точность сообщений об ошибках. Включение определений делает их уязвимыми от макросов и объявлений в контексте использования.
Следовательно, большие шаблоны и шаблоны со сложными контекстными зависимостями лучше компилировать отдельно. Кроме того, это позволяет пользовательскому коду избавиться от влияния обьявлений, требующихся для определенна шаблона. Я считаю подход с включением лишь объявлений шаблонов в пользовательский код и раздельной компиляцией определений шаблонов идеальным. Однако практическое воплощение идеалов часто сдерживается практическими ограничениями, к тому же раздельная компиляция шаблонов на некоторых реализациях является слишком громоздкой и дорогой операцией.
Независимо от того, какая стратегия используется, невстраиваемые статические члены (5С.13.1) должны иметь уникальные определения в некоторых единицах трансляции. Это подразумевает, что такие члрны лучше не использовать в шаблонах, если последние предполагается включать во многие единицы трансляции. Идеальным является код, который одинаково хорошо работает как в случае, когда он компилируется из одной единицы трансляции, или собирается из нескольких независимо компилируемых единиц. К такому идеалу нужно стремиться, ограничивая зависимость определений шаблонов от контекста, и не пытаясь втискивать все это в единый процесс конкретизации. ! Многие компиляторы (в частности фирмы Мгсгозой) на платформе УУ)пдоиз не подаерживают это ключевое слово.
— Прим. ред. Глава 13. Шаблоны 430 13.8. Советы 1. 2. 3. 4. 5. б. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. Используйте шаблоны для представления алгоритмов, применимых к разным типам аргументов; 813.3. Используйте шаблоны для реализации контейнеров; 813.2. Для минимизации размера кода создавайте специализации для контейнеров указателей; 813.5. Всегда обьявляйте общую форму шаблона до его специализаций; 813.5.
Объявляйте специализацию до ее использования; 813.5. Минимизируйте зависимость определения шаблона от контекста его конкре- тизации; $13.2.5, вС.13.8. Определяйте все объявленные специализации; 8!3.5. Подумайте, нужны ли специализации вашего шаблона для строк и массивов языка С; в13.5.2. Параметризуйте шаблоны аргументами, задающими различные варианты по- ведения; 813.4. Для предоставления единого интерфейса к разным реализациям шаблона для различных типов используйте специализации и перегрузку; 813.5.
Предоставляйте простой интерфейс для простых случаев и применяйте пере- грузку и аргументы по умолчанию для более специальных случаев; 8!3.5, в13.4. Отлаживайте конкретные примеры до их обобщения в шаблоны; 813.2.1. Не забывайте про ключевое слово ехрогг в случаях, когда определения шабло- нов нужно компоновать с другими единицами трансляции; 813.7. Отдельно компилируйте большие шаблоны и шаблоны с нетривиальными за- висимостями от контекста; 813.7. Используйте шаблоны, чтобы выразить преобразования, но делайте это ос- мотрительно; 813.6.3.1.
При необходимости ограничивайте аргументы шаблона с помощью функ- ции-члена соязгга1ягО; ~13.9[161, вС.13.10. Для минимизации времени компиляции используйте явную конкретизацию; 5С.13.10. Когда скорость исполнения имеет первостепенную важность, применяйте шаблоны вместо наследования; 813.6.1. Используйте механизм наследования вместо шаблонов, когда важно иметь возможность добавления новых вариантов без перекомпиляции клиентского кода; 813.6.1.
Отдавайте предпочтение шаблонам перед наследованием, когда невозможно определить общий базовый класс; 8!3.6.!. Отдавайте предпочтение шаблонам перед наследованием, когда использова- ние встроенных типов и структур важно из соображений совместимости; 813.6.1. 13.9. Упражнения 431 13.9. УПРажНЕНИЯ 1. 2. 3. 4.
5. б. 8. 9. 10. 11. 12. 13. (*2) Исправьте ошибки в определении АЫ из э13.2.5 и напишите С++-код, эквивалентный тому, что сгенерирует компилятор из определения ХЫ и функцииг() . Протестируйте работу обоих версий кода. Если вы можете это сделать, то сравните их машинные коды.
(*3) Напишите классовый шаблон для односвязного списка с элементами, производными от класса Еш?г, содержащего информацию для связывания элементов. Такой список называется интрузивным. Отталкиваясь от этого списка, напишите код односвязного списка, который может иметь элементы любого типа (неинтрузивный список). Сравните производительность обоих списков и обсудите их достоинства и недостатки. (*2.5) Напишите интрузивный и неинтрузивный двусвязные списки.