Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 32
Текст из файла (страница 32)
для за. висимых неполных имен. 10.3.2. Точки иистаицироваиия Как уже было показано, в исходном коде, использующем шаблон, есть места, в которых компилятор С++ должен иметь доступ к объявлению или определению зтого шаблона. Точка инсталлирования (роля оГ (пмапбабоп — РО1) создается в том случае, когда иекоторал конструкция исходного кода ссылается на специализацию шаблона таким образом, что для зтой специализации н)окно выполнить инстанцирование шаблона. Точка инстанцнрования — зто место кода, в которое можно вставить шаблон с подставленными аргументами. с1авв Му1пе риЫз.с: Му1пе(йпе 1); ); Му1пе орекаеог — (Му1пе сопвей); Ьоо1 орекагог > (Му1пг сопвсй, Му1пс соплей) Сурес)ей Му1пе Хпс; сегар1асе<сурепате т> сои Е (Т 1) ( йг (' > О) ( д(-1); // (1) чоЫ д(1пе) ( // (2) 1<1пс>(42)г // Точка вызова // (3) // (4) 172 Глава 10.
Инстанцирование Когда компилятор С++ встречает вызов шаблона функции Т<1пв> (42), он знает, что нужно инстанцировать этот шаблон, подставив вместо параметра Т тип (Чу1пв. В результате создается точка инстанцирования. Точки (2) и (3) находятся совсем рядом с местом вызова, однако они не могут быть точками инстанцирования, потому что в языке С++ в этих точках нельзя вставить определение:: б<1пт> (1пс) . Главное различие между точками (1) и (4) заключается в том, что в точке (4) функция д (1пк) находится в области видимости, поэтому становится разрешимым вызов д (-1) . Если бы точка (1) была точкой инстанцирования, то этот вызов нельзя было бы разрешить, поскольку в этой точке функция д(1пс) еще не видна.
К счастью, в С++ определяется, что точка инстанцирования для ссылки на специализацию шаблона, не являющегося шаблоном класса, должна находиться сразу после ближайшего определения или объявления области видимости, в котором содержится эта ссылка. В нашем примере это точка (4). Возможно, вас удивит, что в этом примере вместо обычного типа 1пс принимает участие тип Му1пг. Дело в том, что на втором этапе поиска имен, который проводится в точке инстанцирования, используется только АИ.. Поскольку с типом 1пк не связано никакое пространство имен, то при его применении поиск в точке инстанцирования не проводился бы и функция д не была бы обнаружена.
Таким образом, код из предыдущеб го примера перестанет компилироваться, если определение типа 1пс заменить таким: турес)еК Тпа 1пк; Если же речь идет о специализации класса, то здесь ситуация меняется. Рассмотрим приведенный ниже пример. Кегар1аее<гурепагве Т> с1авв Я ( ри)>11с: Т гв; // (5) ипв1дпед 1опд )з() ( // (б) геспгп (ппвйдпед 1опд)вйкеой(Я<1пс>); // (7) // (8) Точки (б) и (7), находящиеся в области видимости функции )з ( ), не могут рассматриваться как точки инстанцирования, поскольку в них не может находиться определение ь В 2002 году Комитет по етандартизаиии языка С++ изучал альтернативы, принятие которыя привело бы к тому, что после рассматриваемой замены определения типа корректность кода сояранияаеь бы.
10.3. Модель инстанцирования С++ 173 пространства имен класса Я<1пс> (шаблоны не могут находиться в области видимости функции). Согласно правилам, определяющим поведение экземпляров, не являющихся классами, точка инстанцнрования могла бы находиться в точке (8). Однако тогда получается, что выражение в1яеой (Я<1пс>) является некорректным, поскольку тогда невозможно было бы определить размер класса Я <1пс >, пока не будет достигнута точка (8). Таким образом, точка инстанцирования для ссылки на генерируемый экземпляр кяасса определяется как точка, находящаяся непосредственно перед ближайшим объявлением пространства имен, которое относится к определению, содержащему ссылку на этот экземпляр.
В нашем примере это точка (5). При фактическом инстанцировании шаблона может возникнуть необходимость дополнительных инстанцирований. Рассмотрим небольшой пример. семр1аке<сурепаме Т> с1авв Я ( рпЬ11с: Суребей Тпп Хз // (1) Семр1аее<еурепаме Т> 1а Т() Я<сЬах>::1 згак1 = 41з сурепаме Я<Т>::1 чак2 = 42; Тпс аайп() ( 8<боиЬ1е>(); // (2) с (2, а), (2, б) В ходе предыдущего рассмотрения уже было установлено, что точка инстанцирования 8<боиЬ1е> находится в точке (2).
В шаблоне функции й () также содержится ссылка на специализацию шаблона класса я<сЬак>, точка инстанцирования которого находится в (1). Кроме того, здесь же имеется и ссылка на шаблон класса Я<Т>, но, поскольку эта ссылка содержит зависимость, выполнить инстанцирование в данной точке не получится. Однако в процессе инстанцирования шаблона функции г<боиЬ1е> в точке (2) можно заметить, что понадобится также инстанцировать опРеделение я<с)оиЬ1е>. Такие вторичные, или транзитивные, точки инстанцирования определяются немного по-другому. для шаблонов, не являющихся шаблонами классов, вторичные точки инстанцирования совпадают с обычными. Для шаблонов классов вторичные точки инстанцирования находятся непосредственно перед первичными (в ближайшем охватывающем пространстве имен).
Для нашего примера это означает, что точка инстанцирования шаблона. функции й<йопЬ1е> может быть помещена в точ- 174 Глава 10. Инстанцирование ку (2,б), а непосредственно перед ней, в точке (2,а), находится вторичная точка инстанцирования шаблона класса Я<доцЬ1е>. Обратите внимание на отличие этой точки инстанцирования от точки инстанцирования шаблона класса Я е сЬак>. Обычно в единице трансляции содержится несколько точек инстанцирования одного и того же экземпляра.
Для экземпляров шаблона класса сохраняется только первая точка инстанцирования, а остальные игнорируются (на самом дене они просто не рассматриваются как точки инстанцирования). Для других экземпляров сохраняются все точки инстанцирования. В любом случае, согласно правилу одного определения„все инстанцирования, которые выполняются в каждой из сохраняющихся точек инстанцирования, должны быть эквивалентными (хотя компилятор С++ не обязан проверять соблюдение этого правила и сообщать о его нарушении). Это позволяет компилятору выбрать для шаблона, не являющегося шаблоном класса, только одну точку, в которой фактически будет происходить инстанцирование.
При этом можно не беспокоиться о том, что инстанцирование в других точках могло бы привести к другому результату. На практике большинство компиляторов откладывают фактическое инстанцирование шаблонов невстраиваемых функций до тех пор, пока не дойдут до конца единицы трансляции. При этом точка инстанцирования соответствующей специапизации шаблона сдвигается в конец единицы трансляции. Разработчики, создающие компиляторы С++, намерены возвести этот метод в ранг документированной реализации, однако в стандарте данный вопрос пока не прояснен. 10.3.3. Модели включения и разделения Где бы ни находилась точка инстанцирования, в этом месте каким-то образом должен быть обеспечен доступ к соответствующему шаблону. Для специализации класса это означает, что определение шаблона класса должно быть видимым в точке, которая находится раньше в данной единице трансляции.
Для точек инстанцирования шаблонов, не являющихся шаблонами класса, это тоже возможно. Обычно определения таких шаблонов просто добавляются в заголовочные файлы, которые с помощью директивы №зпс1пде включаются в единицу трансляции. Такая модель, применяемая к определениям шаблонов, называется моделью включения ()пс!ояоп люде!), и во время написания книги это был один из наиболее популярных подхбдов.
Для точек инстанцирования шаблонов, не являющихся шаблонами классов, существует альтернативный метод: такие шаблоны можно объявлять с помощью директивы ехрогс и определять в другой единице трансляции. Этот подход известен как модель разделения (зерагайоп шоде1). В приведенном ниже фрагменте кода эта модель проиллюстрирована на примере уже знакомого нам шаблона функции вах ( ) . // Единица трансляции 1 ° №зпс1пде с1овсгеав> ехрогк Кевр1аке<курепаве Т> т сопвга вах(т сопвса, т сопвса) 10.3. Модель инстанцирования С++ 175 1пс пьзхп ( ) ( вес)::соус «пах(7, 42) «веб::епд1; // (1) ) // Единица трансляции 2~ ехрогс сетр1асе<сурепазае Т> т сопвса шах(Т сопиев а, Т сепией Ь) гегпгп а<Ь ? Ь : а; ) // (2) Транслируя первый файл, компилятор обнаружит, что в точке (1) находится инструкция, создающая точку инстанцирования, в которой вместо параметра Т подставляется тип Тпс. После этого компилятор должен убедиться в том, что определение во втором файле инстанцировано для удовлетворения этой точки инстанцирования.
10.3.4. Поиск в единицах трансляции Предположим, что первый файл приведенного выше примера переписан, как показано ниже. // Единица трансляции 1: ((Тпс1иде <Товегеат> ехрогс сешр1аге<сурепаше Т> Т сопвсй шах(Т сопвса, Т соплей); пщаеврасе И ( с1авв 1 ( риЬ11с: 1(хпг 1): ч(1) () хпс ч~ Ьоо1 орегаеог < (1 сопвса а, 1 сопвса Ь) гегпгп а.ч < Ь.ч; Тпс иахп() ( Сс),,сопс «заах()Ч::1(7) «вес)::еп61. // (3) ) В точке инстанцирования, которая создается в положении (3), снова нужен доступ к определению, солержащемуся во втором файле (единица трансляции 2).
Однако в этом 17б Глава 10. Инстанцирование определении используется перегруженный оператор е, который обьявлен в единице трансляции 1 и который не видим в единице трансляции 2. Понятно, что для того, чтобы этот пример был работоспособным, процесс инстанцирования должен обратиться к двум разным контекстам объявлений~. Первый контекст — тот, в котором определен шаблон, а второй — тот, в котором объявлен тип 1. Чтобы вовлечь в процесс инстанцирования оба этих контекста, имена шаблонов просматриваются в два этапа (см.
раздел 10.3.1). На первом этапе происходит синтаксический анализ шаблона (другими словами, компилятор С++ первый раз производит разбор его определения). На этом этапе выполняется поиск независимых имен с применением правил обычного поиска и АЭЬ. Кроме того, с помощью правил обычного поиска просматриваются неполные имена зависимых функций (т.е. функций, аргументы которых являются зависимыми). Полученный результат заносится в память, причем при этом не предпринимаются попытки разрешить перегрузку — это происходит на втором этапе. Второй этап выполняется в точке инстанцирования.