Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 39
Текст из файла (страница 39)
Одним из активных сторонников этой ~очки зрения был Билл Гиббонс (В(П 0(ЬЬопз), работавший в то время над компилятором 4 заметим, что этв пеРеменная также ассоциирована с классом 3, поскольку этот класс представляет собой аРгуиеит шаблона типа переменной и. Глава 11. Вывод аргументов шаблонов Таййепц поскольку устранение зависимости от порядка инстанцирования обеспечивало возможность новых интересных сред разработки С++, для которых предполагалось использовать компилятор Тайяепь Однако для работы метода Бартона-Нэкмана требовалось, чтобы возможность введения функции-друга в глобальную область видимости была сохранена в языке в ее текущем (ослабленном) виде. Интересно отметить, что многие слышали о методе Бартона-Нэкмана, но мало кто связывает этот термин с описанной здесь методикой.
В результате в литературе можно найти описание многих других методов, использующих функции-друзья и шаблоны, которые совершенно неверно называют методом Бартона-Нэкмана (см., например, раздел 16.5, стр. 324). Глава 12 Специализация и перегрузка Сейчас вы уже знаете, как шаблоны С++ обеспечивают расширение обобщенного определения в семейство связанных классов или функций. Хотя это и мощный механизм, существует много ситуаций, в которых при замене параметров шаблона обобщенная форма работы далека от оптимальной.
Язык С++ кое в чем уникален, он поддерживает обобщенное программирование, поскольку обладает богатым набором возможностей, позволяющих осуществлять прозрачную подмену обобщенного определения более специализированными. В этой главе представлены два механизма языка С++, которые позволяют реализовать полезные отступления от обобщенного подхода: специализация шаблона и перегрузка шаблонов функций. 12.1. Когда обобщенный код не совсем хорош Рассмотрим приведенный ниже пример.
Сезар1аге<гурепате Т> с1авв Аггау ( ргзчасе: Т* с)ага; риЬ13.с: Аггау(Аггау<Т> сопвгй); Аггау<Т>а орегагог = (Аггау<Т> сопвса); l чойб ехсЬапде мзсЬ(Аггау<Т>* Ь) ( Т* Сшр = дага; баса = Ь->с)ага; Ь->Йаса = сыр; ) Та орегагог[] (взае С й) ( гесигп с)аса[)с]; Глава 12. Специаяизация и перегрузка Севр1аке<турепаве Т> 1п11пе згойс) ехсЬапде(Т* а, Т* Ь) ( Т Свр(*а) з *а = *Ь; *Ь = сврз 12.1.1. Прозрачная настройка В предыдущем примере функция-член ехсЬапде мсс1з () обеспечивааа эффективную альтернативу обобщенной функции ехсЬапде ( ) .
Тем не менее по ряду причин использование другой функции неудобно. 1. Пользователи класса Актау должны поМнить о дополнительном интерфейсе и по возможности аккуратно им пользоваться. 2. Обобщенные алгоритмы могут не уметь отличать различные возможные варианты действий. Севр1аее<гурепаве Т> згоЫ депегйс а1дотйСЬв(Т* х, Т* у) ( ехсЬапде(х, у) ; // Каким образом выбрать // правильный алгоритм? ) По этим соображениям шаблоны С++ обеспечивают прозрачные способы настройки шаблонов функций и классов. Для шаблонов функций это достигается через механизм перегрузки.
Например, можно написать перегруженный набор шаблонов функций дитс)с ехсЬапде() как показано ниже. севр1аее<курепаве Т> 1п11пе зго1г) с)птс)< ехсЬапде(Т* а, Т* Ь) ( Т Свр(*а) р *а — >Ъз // (1) Для простых типов обобщенная реализация функции ехсЬапде ( ) работает хорошо. Однако для типов со сложными операциями копирования обобщенная реализация может быль значительно более ресурсоемкой (в аспекте использования как машинного времени, так и памяти), чем реализация, настроенная под конкретную структуру данных. В нашем примере обобщенная реализация требует одного вызова конструктора копирования шабяона Аггау<Т> и двух вызовов его оператора копирующего присвоения.
Для больших структур ддзшых создание таких копий часто сопровождается копированием опюсительно больших объемов памяти. Однако функциональность ехсЬапде () часто может замениться просто обменом указателями, подобно тому, как это делается в функции-члене ехсЬапде мйсЬ ( ] . 207 12.1. Когда обобщенный код не совсем хорош *ь = свр; Сетар1апе<пурепаше Т> 1п11пе уоЫ дийс)< ехс)тепдо(Агтау<Т>* а, Агкау<Т>* )э) // (2) ( а->ехс)тапде ьг1с)т( э); чей с)ето(Аггау<йпс>* р1, Акгау<йпс>* р2) йпс х, у; ,с(пйс)с ехс)тапде(ах, ау); // использует (1) с(ц1с)с ехс)тапде(р1, р2); // использует (2) Первый вызов дпзс)с ехс)тапде () имеет два аргумента типа 1пс*, поэтому вывод аргументов выполняется успешно только для первого шаблона (обьявленного в точке (1)), когда тип т заменяется типом 1пк.
Поэтому не возникает сомнений относительно того, какую функцию нужно вызвать. Второй же вызов соответствует обоим шаблонам: жизнеспособные функции для вызова с)и1с1с ехс)шпде (р1, р2) получаются как заменой Аггау<1пг> на Т в первом шаблоне, так и заменой 1пс во втором шаблоне. Кроме того, обе замены дают функции с типами параметров, которые точно соответствуют типам аргументов во втором вызове. Обычно зто позволяет заключить, что вызов неоднозначен, однако (как выяснится позже) язык С++ считает второй шаблон "более специализированным", чем первый.
При прочих равных условиях разрешение перегрузки отдает предпочтение более специализированному шаблону и поэтому выбирает шаблон из точки (2). 12.1.2. Семантическая прозрачность Использование перегрузки, как было показано в предыдущем разделе, очень полезно при достижении прозрачной настройки процесса инстанцирования. При этом важно понимать, что такая "прозрачность" существенно зависит от деталей реализации. Чтобы проиллюстрировать это, рассмотрим реализацию нашей функции срззс1< ехс)тапде ( ) . Хотя и обобщенный алгоритм, и алгоритм, настроенный для типов Актау Т>, заканчиваются обменом значений, на которые указывают указатели, побочные эффекты этих операций существенно отличаются.
Яркой иллюстрацией тому может служить код, который сравнивает обмен структурных объектов с обменом шаблонов Аггау<Т>. всгпсс Я ( 1пс х; ) в1, в2; драв)т(Акгау<епк> а1, Актау ( * р = аа1(о)' Глава 12. Специализация и перегрузка 208 йпс* д = йв1.х; а1[0) = в1.х = 1; а2[0) = в2.х = 2; с(ийсй ехсЬапде(йа1, йа2); // после этого // (все еве) дийс)с ехсЬапде(йв1, йв2); // после этого *р== 1 Этот пример показывает, что после вызова дийс)с ехсЬапде ( ) указатель р на первый массив Аггау становится указателем на второй массив.
Однако указатель на объект в1, не являющийся массивом, продолжает указывать в структуру в1 даже после выполнения операции обмена. Единственное изменение — поменялись местами значения, на которые указывают указатели. Это весьма существенное отличие, которое может сбивать с толку пользователей шаблона.
Применение префикса с(ийс)с позволяет привлечь внимание к тому, что данная реализация представляет собой сокращенный вариант нужной операции. Однако первоначальный обобщенный шаблон ехспапде() может при этом содержать оптимизацию для шаблонов Аггау<Т>. Преимущество этой версии обобщенного кода заключаеюя в том, что при этом не требуется создавать потенциально большой временный массив Аггау<Т>. Шаблон ехсЬлпде П вызывается рекурсивно, чем достигается хорошая производительность даже для таких типов, как Аггау<дггау<сЬаг». Отметим также, что более специализированная версия шаблона не объявляется встроенной, поскольку выполняет значительный обьем работы.
В то же время первоначальная обобщенная реализация является встроенной, поскольку выполняет только несколько операций (каждая из которых потенциально ресурсоемка). 12.2. Перегрузка шаблонов функций В предылушем разделе было показано, что возможно сосуществование двух шаблонов функций с одним и тем же именем, даже если они могут быль инстанцированы с параметрами идентичных типов. Приведем еше один простой пример этого.
// с)ега11в/йппсочег1оас(.Ьрр Севр1асе<сурепаве Т> Тпг б(т) Гевр1асе<сурепаве Т> чойг) ехсЬапде(Аггау<Т>* а, Аггау<Т>* Ь) ( Т* р = йа[0); Т* д > йЬ[0)/ бог (вйхе Г й = а->в1ае(); --)<)= 0; ) ехсЬапде(р++, д++)/ р( у У 5[,/(('/1 Н<г ( ~~,,', б~р"йы ~7~ ~-Р((". ) 2.2. Перегрузка шаблонов функций 209 кепикп 1 Г севр1асе<сурепаве Т> з пт й (Т*) ( кетикп 2; Когда тип Т заменяется типом 1пс* в первом шаблоне, получается функция, у которой есть точно такие же типы параметров (и возвращаемых значений), что и у функции, получаемой при замене типа 1пс типом Т во втором шаблоне.
Сосуществовать могут не только эти шаблоны, но и их экземпляры, даже если у них идентичны типы параметров и возвращаемых значений. Приведенный ниже пример демонстрирует, как можно вызвать две такие сгенерированные функции с помощью синтаксиса явного аргумента шаблона (в предположении предыдуших объявлений шаблона). // с)еса11н/Типсочек1оас). срр ()1пс1ис)е <1онскеав> Ф1пс1ис)е чйипсочек1сас).)зррв 1пс ваз.п() вка:: С ° Т<1пеь>((1пк*) О) «нва::епа1з нМ: зсоип «Тс1пп>((1пп*) 0) «нМ: сенс)1г Результатом выполнения этой программы будет следующий вывод: Чтобы объяснить работу программы, детально проанализируем вызов й<1пс*> ( (1пс+) О) . Синтаксис Т<1пс*> обозначает, что первый параметр шаблона Т нужно заменить значением типа 1пс* без использования вывода аргумента шаблона.