Главная страница
Навигация по странице:

  • 10.5.1. Модель компиляции с включением

  • 10.5.2. Модель компиляции с разделением

  • 10.5.3. Явные объявления конкретизации

  • Язык программирования C++. Вводный курс. С для начинающих


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница46 из 93
    1   ...   42   43   44   45   46   47   48   49   ...   93
    486
    10.5.
    Модели компиляции шаблонов А
    Шаблон функции задает алгоритм для построения определений множества экземпляров функций. Сам шаблон не определяет никакой функции. Например, когда компилятор видит шаблон:
    } он сохраняет внутреннее представление min(), но и только. Позже, когда встретится ее реальное использование, скажем: double dobj = min( i, j ); компилятор строит определение min() по сохраненному внутреннему представлению.
    Здесь возникает несколько вопросов. Чтобы компилятор мог конкретизировать шаблон функции, должно ли его определение быть видимо при вызове экземпляра этой функции?
    Например, нужно ли определению шаблона min() появиться до ее конкретизации c целыми параметрами при инициализации dobj? Следует ли помещать шаблоны в заголовочные файлы, как мы поступаем с определениями встроенных (inline) функций?
    Или в заголовочные файлы можно помещать только объявления шаблонов, оставляя определения в файлах исходных текстов?
    Чтобы ответить на эти вопросы, нам придется объяснить принятую в C++ модель
    компиляции шаблонов, сформулировать требования к организации определений и объявлений шаблонов в программах. В C++ поддерживаются две таких модели: модель с включением и модель с разделением. В данном разделе описываются обе модели и объясняется их использование.
    10.5.1.
    Модель компиляции с включением
    Согласно этой модели мы включаем определение шаблона в каждый файл, где этот шаблон конкретизируется. Обычно оно помещается в заголовочный файл, как и для встроенных функций. Именно такой моделью мы пользуемся в нашей книге. Например:
    } template
    Type min( Type t1, Type t2 )
    { return t1 < t2 ? t1 : t2; int i, j;
    // model1.h
    // модель с включением:
    // определения шаблонов помещаются в заголовочный файл template
    Type min( Type t1, Type t2 ) { return t1 < t2 ? t1 : t2;

    С++ для начинающих
    487
    Этот заголовочный файл включается в каждый файл, где конкретизируется функция min()
    : double dobj = min( i, j );
    Заголовочный файл можно включить в несколько файлов с исходными текстами программы. Означает ли это, что компилятор конкретизирует экземпляр функции min() с целыми параметрами в каждом файле, где имеется обращение к ней? Нет. Программа должна вести себя так, словно min() с целыми параметрами определена только один раз.
    Где и когда в действительности конкретизируется шаблон функции, оставляется на усмотрение разработчика компилятора. Нам достаточно знать, что где-то в программе нужная функция min() была конкретизирована. (Как мы покажем далее, с помощью явного объявления конкретизации можно указать, где и когда оно должно быть выполнено. Такие объявления желательно использовать на поздних стадиях разработки продукта для улучшения производительности.)
    Решение включать определения шаблонов функций в заголовочные файлы не всегда удачно. Тело шаблона описывает детали реализации, которые пользователям не интересны или которые мы хотели бы от них скрыть. В действительности, если определение шаблона велико, то количество кода в заголовочном файле может превысить разумные пределы. Кроме того, многократная компиляция одного и того же определения при обработке разных файлов увеличивает общее время компиляции программы.
    Отделить объявления шаблонов функций от их определений позволяет модель компиляции с разделением. Посмотрим, как ее можно использовать.
    10.5.2.
    Модель компиляции с разделением
    Согласно этой модели объявления шаблонов функций помещаются в заголовочный файл, а определения – в файл с исходным текстом программы, т.е. объявления и определения шаблонов организованы так же, как в случае с невстроенными (non-inline) функциями.
    Например:
    Type min( Type t1, Type t2 ) { /* ... */ }
    Программа, которая конкретизирует шаблон функции min(), должна предварительно включить этот заголовочный файл:
    // определения шаблонов включены раньше
    // используется конкретизация шаблона
    #include "model1.h" int i, j;
    // model2.h
    // модель с разделением
    // сюда помещается только объявление шаблона template Type min( Type t1, Type t2 );
    // model2.C
    // определение шаблона export template

    С++ для начинающих
    488
    double d = min ( i, j ); // правильно: здесь производится конкретизация
    Хотя определение шаблона функции min() не видно в файле user.c, конкретизацию min(int,int)
    произвести можно. Но для этого шаблон min() должен быть определен специальным образом. Вы уже заметили, как именно? Если вы внимательно посмотрите на файл model2.c, то увидите, что определению шаблона функции min() предшествует ключевое слово export. Таким образом, шаблон min() становится экспортируемым.
    Слово export говорит компилятору, что данное определение шаблона может понадобиться для конкретизации функций в других файлах. В таком случае компилятор должен гарантировать, что это определение будет доступно во время конкретизации.
    Для объявления экспортируемого шаблона перед ключевым словом template в его определении надо поместить слово export. Если шаблон экспортируется, то его разрешается конкретизировать в любом исходном файле программы – для этого нужно лишь объявить его перед использованием. Если слово export перед определением опущено, то компилятор может и не конкретизировать экземпляр функции min() с целыми параметрами и нам не удастся связать программу.
    Обратите внимание, что в некоторых реализациях это ключевое слово не нужно, поскольку поддерживается расширение языка, согласно которому неэкспортированный шаблон функции может встречаться только в одном исходном файле, при этом экземпляры такого шаблона в других файлах конкретизируются правильно. Однако подобное поведение не соответствует стандарту, который требует, чтобы пользователь всегда помечал определения шаблонов функций как экспортируемые, если объявление шаблона видно в исходном файле до его конкретизации.
    Ключевое слово export в объявлении шаблона, находящемся в заголовочном файле, можно опустить. Так, в объявлении min() в файле model2.h этого слова нет.
    Шаблон функции должен быть определен как экспортируемый только один раз во всей программе. К сожалению, поскольку компилятор обрабатывает файлы один за другим, он обычно не замечает, что шаблон определен как экспортируемый в нескольких исходных файлах. В результате подобного недосмотра может произойти следующее:

    при редактировании связей возникает ошибка, показывающая, что шаблон функции определен более, чем в одном файле;

    компилятор несколько раз конкретизирует шаблон функции с одним и тем же множеством аргументов, что приводит к ошибке повторного определения функции при связывании программы;

    компилятор может конкретизировать шаблон с помощью одного из его экспортированных определений, игнорируя все остальные.
    Нельзя с уверенностью утверждать, что наличие в программе нескольких экспортируемых определений шаблона функции обязательно вызовет ошибку. При организации программы надо быть внимательным и следить за тем, чтобы подобные определения размещались только в одном исходном файле.
    Модель с разделением позволяет отделить интерфейс шаблонов функций от его реализации и организовать программу так, что интерфейсы всех шаблонов помещаются в заголовочные файлы, а реализации – в файлы с исходным текстом. Однако не все
    // user.C
    #include "model2.h" int i, j;

    С++ для начинающих
    489
    компиляторы поддерживают такую модель, а те, которые поддерживают, не всегда делают это правильно: модель с разделением требует более изощренной среды программирования, которая доступна не во всех реализациях C++. (В другой нашей книге, “Inside C++ Object Model”, описан механизм конкретизации шаблонов, поддержанный в одной из реализаций C++, а именно в компиляторе Edison Design
    Group.)
    Поскольку приводимые нами примеры работы с шаблонами невелики и поскольку мы хотим, чтобы они компилировались максимально большим числом компиляторов, мы ограничились использованием модели с включением.
    10.5.3.
    Явные объявления конкретизации
    При использовании модели с включением определение шаблона функций включается в каждый исходный файл, где встречается конкретизация этого шаблона. Мы отмечали, что, хотя неизвестно, где и когда понадобится шаблон функции, программа должна вести себя так, как будто экземпляр шаблона для данного множества аргументов конкретизирован ровно один раз. В действительности некоторые компиляторы (особенно старые) конкретизируют шаблон функции с данным множеством аргументов шаблона неоднократно. В рамках этой модели для использования на этапе сборки или на одной из предшествующих ей стадий выбирается один из конкретизированных экземпляров, а остальные игнорируются.
    Результат работы программы не зависит от того, сколько раз конкретизировался шаблон: в конечном итоге используется лишь один экземпляр. Но если приложение состоит из большого числа файлов, то время компиляции приложения заметно возрастает.
    Подобные проблемы, характерные для старых компиляторов, затрудняли использование шаблонов. Поэтому в стандарте C++ введено понятие явного объявления конкретизации, помогающее программисту управлять моментом, когда конкретизация происходит.
    В явном объявлении конкретизации за ключевым словом template идет объявление шаблона функции, в котором его аргументы указаны явно. Рассмотрим шаблон sum(int*, int)
    : template int* sum< int* >( int*, int );
    Здесь в качестве аргумента явно задается int*. Явное объявление конкретизации с одним и тем же множеством аргументов шаблона может встречаться в программе не более одного раза.
    Определение шаблона функции должно находиться в том же файле, где и явное объявление конкретизации. Если же его не видно, то явное объявление приводит к ошибке: template
    Type sum( Type op1, Type op2 ) { /* ... */ }
    // явное объявление конкретизации

    С++ для начинающих
    490
    template VI sum< VI >( VI , int );
    Если в некотором исходном файле встречается явное объявление конкретизации, то что произойдет в других файлах, где используется такая же конкретизация шаблона функции? Как сказать компилятору, что явное объявление находится в другом файле и что при использовании в этом файле шаблон конкретизировать не надо?
    Явные объявления конкретизации используются в сочетании с опцией компилятора, которая подавляет неявную конкретизацию шаблонов. Название опции в разных компиляторах различно. Например, в VisualAge for C++ для Windows версии 3.5 фирмы
    IBM эта опция называется /ft-. Если приложение компилируется с данной опцией, то компилятор предполагает, что шаблоны будут конкретизироваться явно, и не выполняет автоматической конкретизации.
    Разумеется, если мы не включили в программу явного объявления конкретизации для некоторого шаблона, но задали опцию /ft-, то при сборке произойдет ошибка из-за того, что функция не была конкретизирована.
    Упражнение 10.8
    Назовите две модели компиляции шаблонов, поддерживаемые в C++. Объясните, как организуются определения шаблонов функций в каждой модели.
    Упражнение 10.9
    Пусть дано следующее определение шаблона функции sum():
    Type sum( Type op1, char op2 );
    Как записать явное объявление конкретизации этого шаблона с аргументом типа string
    ?
    10.6.
    Явная специализация шаблона А
    Не всегда удается написать шаблон функции, который годился бы для всех возможных типов, с которыми он может быть конкретизирован. В некоторых случаях имеется специальная информация о типе, позволяющая написать более эффективную функцию, чем конкретизированная по шаблону. А иногда общее определение, предоставляемое шаблоном, для некоторого типа просто не работает. Рассмотрим, например, следующее определение шаблона функции max():
    #include template
    Type sum( Type op1, int op2 ); // только объявление
    // определяем typedef для vector< int > typedef vector< int > VI;
    // ошибка: sum() не определен template

    С++ для начинающих
    491
    }
    Когда этот шаблон конкретизируется с аргументом типа const char*, то обобщенное определение оказывается семантически некорректным, если мы интерпретируем каждый аргумент как строку символов в смысле языка C, а не как указатель на символ. В этом случае необходимо предоставить специализированное определение для конкретизации шаблона.
    Явное определение специализации – это такое определение, в котором за ключевым словом template следует пара угловых скобок <>, а за ними – определение специализированного шаблона. Здесь указывается имя шаблона, аргументы, для которых он специализируется, список параметров функции и ее тело. В следующем примере для max(const char*, const char*)
    определена явная специализация: return ( strcmp( s1, s2 ) > 0 ? s1 : s2 );
    Поскольку имеется явная специализация, шаблон не будет конкретизирован с типом const char*
    при вызове в программе функции max(const char*, const char*). При любом обращении к max() с двумя аргументами типа const char* работает специализированное определение. Для любых других обращений функция сначала конкретизируется по обобщенному определению шаблона, а затем вызывается. Вот как это выглядит:
    }
    // обобщенное определение шаблона template
    T max( T t1, T t2 ) { return ( t1 > t2 ? t1 : t2 );
    #include
    // явная специализация для const char*:
    // имеет приоритет над конкретизацией шаблона
    // по обобщенному определению typedef const char *PCC; template<> PCC max< PCC >( PCC s1, PCC s2 ) {
    #include
    // здесь должно быть определение шаблона функции max()
    // и его специализации для аргументов const char* int main() {
    // вызов конкретизированной функции: int max< int >( int, int ); int i = max( 10, 5 );
    // вызов явной специализации:
    // const char* max< const char* >( const char*, const char* ); const char *p = max( "hello", "world" ); cout << "i: " << i << " p: " << p << endl; return 0;

    С++ для начинающих
    492
    Можно объявлять явную специализацию шаблона функции, не определяя ее. Например, для функции max(const char*, const char*) она объявляется так: template< > PCC max< PCC >( PCC, PCC );
    При объявлении или определении явной специализации шаблона функции нельзя опускать слово template и следующую за ним пару скобок <>. Кроме того, в объявлении специализации обязательно должен быть список параметров функции: template<> PCC max< PCC >;
    Однако здесь можно опускать задание аргументов шаблона, если они выводятся из формальных параметров функции: template<> PCC max( PCC, PCC );
    В следующем примере шаблон функции sum() явно специализирован: template<> int sum( char, char );
    Пропуск части template<> в объявлении явной специализации не всегда является ошибкой. Например:
    // объявление явной специализации шаблона функции
    // ошибка: неправильные объявления специализации
    // отсутствует template<>
    PCC max< PCC >( PCC, PCC );
    // отсутствует список параметров
    // правильно: аргумент шаблона const char* выводится из типов параметров template
    T1 sum( T2 op1, T3 op2 );
    // объявления явных специализаций
    // ошибка: аргумент шаблона для T1 не может быть выведен;
    // он должен быть задан явно template<> double sum( float, float );
    // правильно: аргумент для T1 задан явно,
    // T2 и T3 выводятся и оказываются равными float template<> double sum( float, float );
    // правильно: все аргументы заданы явно

    С++ для начинающих
    493
    const char* max( const char*, const char*);
    Однако эта инструкция не является специализацией шаблона функции. Здесь просто объявляется обычная функция с типом возвращаемого значения и списком параметров, которые соответствуют полученным при конкретизации шаблона. Объявление обычной функции, являющееся конкретизацией шаблона, не считается ошибкой.
    Так почему бы просто не объявить обычную функцию? Как было показано в разделе 10.3, для преобразования фактического аргумента функции, конкретизированной по шаблону, в соответствующий формальный параметр в случае, когда этот аргумент принимает участие в выводе аргумента шаблона, может быть применено лишь ограниченное множество преобразований типов. Точно так же обстоит дело и в ситуации, когда шаблон функции специализируется явно: к фактическим аргументам функции при этом тоже применимо лишь ограниченное множество преобразований. Явные специализации не помогают обойти соответствующие ограничения. Если мы хотим выйти за их пределы, то должны определить обычную функцию вместо специализации шаблона. (В разделе 10.8 этот вопрос рассматривается более подробно; там же показано, как работает разрешение перегруженной функции для вызова, который соответствует как обычной функции, так и экземпляру, конкретизированному из шаблона.)
    Явную специализацию можно объявлять даже тогда, когда специализируемый шаблон объявлен, но не определен. В предыдущем примере шаблон функции sum() лишь объявлен к моменту специализации. Хотя определение шаблона не обязательно, объявление все же требуется. То, что sum() – шаблон, должно быть известно до того, как это имя может быть специализировано.
    Такое объявление должно быть видимо до его использования в исходном файле.
    Например: template<> PCC max< PCC >(PCC s1, PCC s2 ) { /* ... */ }
    // обобщенное определение шаблона template
    T max( T t1, T t2 ) { /* ... */ }
    // правильно: обычное объявление функции
    #include
    #include
    // обобщенное определение шаблона template
    T max( T t1, T t2 ) { /* ... */ } int main() {
    // конкретизация функции
    // const char* max< const char* >( const char*, const char* ); const char *p = max( "hello", "world" ); cout << "p: " << p << endl; return 0;
    }
    // некорректная программа: явная специализация const char *:
    // имеет приоритет над обобщенным определением шаблона typedef const char *PCC;

    С++ для начинающих
    1   ...   42   43   44   45   46   47   48   49   ...   93


    написать администратору сайта