Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 89
Текст из файла (страница 89)
Эти ситуации создают так называемые точки инстанцирования (см. раздел 10.3.2, стр. 171). Встраиваемые функции должны быть определены в каждой единице трансляции, в которой они используются (в которой они вызываются или в которой определяются их адреса). Однако, в отличие от типов классов, их определение может идти после точки использования.
1п1зпе 1пс пот во Санс(); зпс тахп() ( пос во Йавт(); хп1зпе хпс пот во Санс() Хотя это правильный код на С++, некоторые компиляторы не встраивают вызов функции с телом, которого еще не видно; поэтому желаемого эффекта можно не достичь. Как и в случае шаблонов классов, использование функции, сгенерированной из параметризованного объявления функции (шаблона функции или функции-члена либо функции-члена шаблона класса), создает точку инстанцировання. Однако, в отличие от типов классов, соответствующее определение может идти после этой точки (или его вообще может не быть, если функция экспортирована). Аспекты О1Ж, о которых идет речь в этом приложении, как правило, легко проверить с помощью компиляторов С++.
Поэтому стандарт С++ требует, чтобы компиляторы выполняли определенную диагностику при нарушении этих правил. Исключением является отсутствие определения для неэкспортированной параметризованной Функции. Такие ситуации обычно не диагностируются. А.З.З. Ограничения эквивалентности единиц перекрестной трансляции Способность определягь некоторые виды объектов в более чем одной единице трансляции создает потенциальнуо возможность возникновения ошибки нового вида: многократные определения, которые не согласуются друг с другом. К сожалению, такие ошибки трудно обнаружить с помощью традиционной технологии компиляции, в которой единицы трансляции обрабатываются по одной за раз.
Таким образом, стандарт С++ не требует обнаружения или диагностирования различий во многократных определениях (но, конечно, нозволяеш делать это). Если это ограничение на единицы перекрестной трансляции наРушается, стандарт С++ квалифицирует это как фактор, ведущий к неопределенному поведе- 500 Приложение А. Правило одного определения нию, что означает возможность любых предсказуемых и непредсказуемых событий.
Обычно такие ошибки, ие поддающиеся диагностике, могут вызывать аварийные ситуации или приводить к неправильным результатам, но в принципе они могут повлечь за собой и другие, более прямые виды ущерба (например, повреждение файлов). Ограничения на единицы перекрестной трансляции указывают, что, когда объект определен в двух различных местах кода, эти два места должны состоять из одинаковой последовательности лексем (ключевых слов, операторов и т.п., оставшихся после обработки препроцессором). Кроме того, в одинаковом контексте эти лексемы должны иметь одинаковое значение (например, может потребоваться, чтобы идентификаторы ссылались на одну и ту же переменную).
Рассмотрим пример. // Единица еранслпцми 1в тгаггс 1пг соппгег = О; 1п11пе чо1с( 1псгеаве соипгег() ++ссипсегг 1пс майн() // Единица ерансппцмм 2: всаейс 1пг соипгег = О; 1п11пе чо1с) 1псгеаве соппсег() ++соппгегг Этот пример выдает сообщение об ошибке, поскольку, хотя последовательность лексем лля встроенной функции 1псгеаве соппсег () выглядит идентично в обеих единицах трансляции, они содержат лексему соипсег, которая указывает на два разных объекта. Действительно, поскольку две переменные соипсег имеют внутреннее связывание (спецификатор всас1с), они никак не связаны между собой, несмотря на одинаковое имя.
Отметим, что это ошибка, несмотря на то что в данном случае не используется ни одна из встроенных функций. Размещение в заголовочных файлах (при необходимости подключаемых к программе с помощью директивы №1пс1иг)ес() определений обьектов, которые могут быть опрелелены в нескольких единицах трансляции, обеспечивает идентичность последовательно- 3 Забавно, что версия 1 компилятора ясс действительно делает это, запуская в таких ситуациях игру Коков.
А.З. Детали правила одного определения 501 сти лексем почти во всех ситуациях . При таком подходе ситуации, в которых две иден- 4 тичные лексемы ссылаются на различные объекты, становятся довольно редкими. Но, если такая ситуация все же случается, возникающие при этом ошибки часто загадочны и их сложно отследить. Ограничения на перекрестные единицы трансляции касаются не только объектов, которые могут быть определены в нескольких местах, но и аргументов по умолчанию в обьявлениях. Иными словами, приведенная ниже программа обладает непредсказуемым поведением. // Единица трансляции 1г ггоЫ ипцвег)(1пс = 3); 1пт лауп() // Единица трансляции 2г чоЫ спинет(1пт = 4); Следует отменив, что с эквивалентностью потоков лексем иногда могут быть связаны неявные тонкие эффекты.
Следующий пример (слегка измененный) взят из стандарта С++. // Единица трансляции 1г с1авв Х ( риЫ1с: Х(1пс)г Х(1пс, 1пт)~ Х::Х(1пе = О) ( ) с1авв )Э: риЬ11с Х ( )' Р 62; // О() вызывает Х(1пс) // Единица трансляции 2г с1авв Х ри)э1з.с г Х(1пк) г Х(1пт, 1пТ); Х:гХ(1пк = О, 1пт = О) 4 Ииогла лирективы условной компиляции по-разному вычисляются в разных единицах трансляции такие лнрекгивы следует использовать осторожно. Возможны также и другие отличия, но они встречаются реже.
Приложение А. Правило одного определения 502 с1авв О: рп)э11с Х ( // О() вызывает Х(1пг,1пг) ): // Неявное определение О() нарушает ООН В этом примере проблема связана с тем, что неявные конструкторы по умолчанию класса О, генерируемые в двух единицах трансляции, отличаются. В одной из них вызывается конструктор х, принимающий один аргумент, в другой вызывается конструктор Х, принимающий два аргумента. Несомненно, данный пример — это еще один стимул ограничить аргумент по умолчанию одним местом в программе (при возможности это место должно быть в заголовочном файле). К счастью, размещение аргумента по умолчанию в определениях за пределами класса — довольно редкое явление.
Существует также исключение из правила о том, что идентичные лексемы должны ссылаться на идентичные объекты. Если идентичные лексемы ссылаются на не связанные между собой константы, которые имеют одно и то же значение, а адрес результирующего выражения не используется, то такие лексемы считаются эквивалентными. Это исключение позволяет использовать следующие программные структуры: // Файл ?зеас)ег.?зрр: №1йпс)ей НЕАОЕН НРР №йеб1пе НЕДОЕВ НРР 1пс сопвг 1епдг)з = 10; с1авв М1п1Вцййег ( сваг Ьпй[1епдс)т)? №епд11 //НЕДОЕВ НРР В принципе, когда этот заголовочный файл включается в две разные единицы трансляции, создаются две отдельные константные переменные под названием 1епдг?~, поскольку в данном контексте сопвк означает всак1с.
Однако такие константные переменные часто предназначены для определения постоянных значений во время компиляции, а не конкретных адресов памяти во время выполнения программы. Следовательно, если нас ничто не заставляет иметь конкретное место в памяти (например, обращение к переменной по адресу), то вполне достаточно иметь одно и то же значение для двух констант. Это исключение из правила эквивалентности 0[Ж применимо только к целочисленным и перечислимым типам (в эту категорию не попадают типы данных с плавающей запятой и указатели). И наконец, замечание о шаблонах. Имена в шаблонах связываются в две фазы.
Так называемые независимые имена связываются в момент, когда определяется шаблон. Для них правила эквивалентности обрабатываются таким же образом, как и для других не- А.З. Детали правила одного определения 503 шаблонных определений.
Для имен, которые связываются в точке инстанцнрования, правила эквивалентности должны быть применимы в этой точке и связывания должны быть эквивалентны. Это приводит к тонкому эффекту. Хотя экспортированные шаблоны определяются только в одном месте, они могут иметь многочисленные инстанцнрования, которые должны подчиняться правилам эквивалентности. Ниже приведен пример чрезвычайно замысловатого нарушения правила ООК.
// Файл Ьеайек.Ьрр: $1бпс)ей НЕА)ЗЕК НРР ()с)ей1пе НЕ)))ЭЕВ НРР епша Со1ог ( ген, дгееп, Ь1пе ); // Пространство имен, связанное с // Со1ог — глобальное пространство имен ехроге Сещр1ате<еурепаще Т> уо1с) ЬйдЫ1дЬс(Т) уо1с) 1п1с()~ ()епс)1Й //НЕАОЕК НРР // Файл Е р1 аей. рр: ((1пс1ис)е "Ьеадег.Ьрр" Екрстс сЕщр1аСЕ<еурЕПатЕ Т> уо1с) ЬйдЫ1дЬс(Т х) ( ра1пс (х); // (1) Зависимый вызов: требуется поиск, // зависящий от аргумента ) // Файл 1л1к.оррз ()1пс1ис)е "Ьеас)ег.Ьрр" пащеврасе ( // Безымянное пространство имен! уо1с) ра1пе(Со1ог с! // (2) ( уо16 1п1с() ( ЬйдЫ1дЬс(Ыие); // Зависящий от аргумента поиск // (1) приводит к (2) Приложение А. Правило одного определения 504 // Файл вш1в.сРР- №1пс1ис)е ")теа№ег.)трр" пашеврасе ( // Безымянное пространство имен! чо1с) ра1пг(Со1ог с) // (3) ( 1пс па 1п ( ) ( 1п1г()г )з1д)т11д)зс(ген)г // Эависящий от аргумента поиск (1) // приводит к (3) ) / Чтобы пеняешь этот пример, необходимо помнить, что функции, определенные в безымянном просгршстве имен, имеют внешнее связывание.
Однако они отличаются от любых функци$, определенных в безымянном пространстве имен других единиц трансляции. Следовательно, две функции райне () различны. Однако у вызова ра1пс () в экспортированном шаблоне есть аргумент, зависящий от шаблона. Следовательно, он не связывается до точки инстанцирования.