А.В. Столяров - Введение в язык Си++ (1114949), страница 26
Текст из файла (страница 26)
В простейшем случае о частичнойспециализации речь идёт, если задаётся конкретное значение одного илинескольких, но не всех параметров шаблона. Так, если у нас имеетсяшаблон Cls, зависящий от двух параметров:tem plate < c la ss A, c la s s В>119c la s s C ls {/ * ... * />;то можно задать специализированный вариант, например, для случая,когда параметр В есть тип in t:tem plate < c la s s Х>c la s s Cls<X, int> {/ * ...
* />;Более сложный случай частичной специализации возникает, когда в заголовке шаблона указывается некий тип (c la s s Т), а в описываемом типеиспользуется указатель или ссылка на Т, например:tem plate < c la s s Z>c la s s Foo {/ * общая реализация шаблона Foo * />;tem plate < c la s s T>c la s s Foo<T*> {/ * специальная реализация Foo для указателей * />;В этом случае мы инструктируем компилятор, что шаблон Foo следуетинстанциировать специальным способом, если в качестве его параметразадан тип-указатель, и обычным способом — если заданный параметруказателем не является.Отметим, что для шаблонов функций (в отличие от шаблонов классов) частичная специализация запрещена, поскольку в сочетании с перегрузкой приводит к нежелательным последствиям.Ещё один немаловажный момент состоит в том, что в тексте программы специализированный (полностью или частично) вариант шаблона всегда должен находиться после общего.§5.4.
К он стан тн ы е в ы р а ж ен и я в ролип ар ам етр о в ш аблон аПараметрами шаблонов могут быть не только типы, но и обычныеконстантные выражения, чаще всего целочисленные. Многие авторы иллюстрируют эту возможность на примере задания границ массивов; так,мы могли бы в нашем шаблоне для сортировки массивов сделать размермассива параметром шаблона, а не функции:120tem plate < c la ss T, in t len>void so rt(T * array) { / * реализация сортировки * / }вот только не совсем понятно, зачем это делать.
Если, скажем, у нас имеются два целочисленных массива разного размера (например, 10 элементов и 15) и мы применим для их сортировки такой вот шаблон, то компилятор сгенерирует две разные функции: so rt< in t,1 0 > и s o r t < in t , 15>, иэти функции будут представлять собой абсолютно одинаковые фрагменты исполняемого кода, отличающиеся только одной константой, тогдакак если применять шаблон в том виде, в котором мы его писали ранее,функция будет одна (so rt<in t>).Существенно интереснее (хотя и намного сложнее) будет другой пример. Зададимся целью создать объект, реализующий N -мерные массивыдля произвольных значений N, которые к тому же будут автоматически изменять свои размеры по любому из измерений, подобно тому, какизменял свою длину массив Int Array, рассмотренный нами в § 2.19.2.
Поскольку нам могут понадобиться N -мерные массивы элементов разныхтипов, реализуем нашу задумку в виде шаблона класса с тремя параметрами: первый параметр будет задавать тип элементов массива, второйпараметр (имеющий тип, совпадающий с типом элемента) укажет, какое исходное значение присваивать элементам массива при их создании,и, наконец, третий параметр — выражение типа in t — будет задаватьколичество измерений массива.
Шаблон будет описывать операцию индексирования, которая возвращает ссылку на объект, заданный такимже шаблоном с теми же фактическими параметрами, только с уменьшенным на единицу количеством измерений. Ясно, что такой вариантне проходит для одномерного массива (не может же он, в самом деле,возвращать ноль-мерный массив, ведь такого не бывает), но здесь нампоможет частичная специализация: для случая количества измерений,равного одному, мы опишем специальный случай нашего шаблона, в котором операция индексирования будет возвращать ссылку на простойэлемент (тип которого задан первым параметром шаблона).С реализацией одномерного случая всё более-менее понятно, достаточно взять уже знакомый нам IntArray и переделать его в шаблон.Что касается всех многомерных случаев, то самый простой способ ихреализации — взять всё тот же динамически расширяемый массив, элементами которого на сей раз будут выступать указатели на объекты,представляющие массив на единицу меньшей размерности (двумерныймассив будет реализован как массив указателей на объекты одномерныхмассивов, трёхмерный — как массив указателей на двумерные, и т.
д.).Для экономии памяти указатели исходно будут нулевыми; соответствующие объекты (N — 1)-мерных массивов будут создаваться только припервом обращении к ним.121Итак, в основе обоих случаев нашего массива (как одномерного, так идвумерного) оказывается одномерный массив, автоматически расширяющийся при необходимости. Дважды реализовывать эту сущность совершенно ни к чему, тем более что в нашем распоряжении имеется механизмшаблонов. Начнём с реализации динамически расширяющегося массивас произвольным типом элементов:tem p late < c la s s Т>c l a s s Array {Т *р ;Т in it;unsigned in t s i z e ;p u b l ic :Array(T in ) : p ( 0 ) , i n i t ( i n ) , siz e (O ) { }"A rray () { i f ( p ) d e le te [] p ; }T& o p e ra to r [] (unsigned in t id x ) {i f ( i d x >= s i z e ) R e s iz e ( id x ) ;re tu rn p [ i d x ] ;}in t S iz e O co n st { re tu rn s i z e ; }p r iv a te :vo id R e siz e (u n sig n e d in t req u ired _ in d e x ) {unsigned in t new _size = siz e = = 0 ? 8 : s i z e ;w hile(new _size <= req u ired _ in d ex)new _size *= 2;T *new _array = new T [n ew _ siz e];fo r(u n sig n e d in t i = 0 ; i < new _size; i+ + )n ew _array[i] = i < s iz e ? p [ i ] : i n i t ;i f ( p ) d e le te [] p ;p = n ew .array ;s iz e = new _size;}/ / запретим копирование и присваиваниеvo id o p e ra to r= (c o n st Array<T>& r e f ) { }A rray (co n st Array<T>& r e f ) { }};Подробно комментировать этот шаблон мы не будем, поскольку реализация почти дословно повторяет реализацию класса IntArray, остановимсятолько на отличиях.
Во-первых, мы добавили здесь поле in it , значениекоторого, задаваемое в конструкторе, присваивается новым (не существовавшим ранее) элементам массива при его расширении. Во-вторых, размер массива исходно принимается нулевой, а не 16, как в IntArray. Нуи, конечно, класс преобразован в шаблон, что тоже существенно.Имея этот класс, мы можем легко реализовать общий случай нашегомногомерного массива.
Назовём этот шаблон MultiMatrix и напомним,122что он должен работать для любого количества измерений, большегоединицы, а одномерный случай мы потом опишем путём специализации.tem plate < c la s s Т, Т i n i t _ v a l , in t dim>c l a s s M ultiM atrix {A rray<M ultiM atrix<T, i n i t _ v a l , d im -l>*> a r r ;p u b l ic :M u ltiM atrix () : arr(O ) { }"M u ltiM a trix () {f o r ( i n t i= 0 ; i < a r r . S i z e O ; i+ + )i f ( a r r [ i ] ) d e le te a r r [ i ] ;}M ultiM atrix<T , i n i t _ v a l , dim-l>&o p e r a t o r [ ] (unsigned in t id x ) {i f ( ! a r r [id x ])a r r [ id x ] = пей M ultiM atrix<T , i n i t _ v a l , d im -l> ;re tu rn * a r r [ i d x ] ;}};Мы воспользовались здесь шаблоном Array для построения массива указателей на элементы типа «(N —1)-мерный массив»; сам этот тип задаётся через сам же шаблон MultiMatrix, причём с теми же параметрами,что и у исходного шаблона, отличается лишь третий параметр, задающийколичество измерений.
Получается, что описание шаблона MultiMatrixв определённом смысле рекурсивно.Объект массива указателей мы назвали arr. Напомним, что классы,построенные по шаблону Array, принимают в качестве параметра конструктора начальное значение, исходно присваиваемое элементам массива; в данном случае этот параметр — константа 0, что означает нулевойуказатель. Исходно все элементы массива нулевые, и только при первом обращении к соответствующему элементу создаётся объект (N —1)мерного массива (см. оператор i f в теле операции индексирования).Наконец, опишем базисный случай — одномерный массив, то естьспециализированный вариант шаблона MultiMatrix для случая, когдатретий параметр шаблона равен единице. Это можно сделать гораздопроще, поскольку новый класс фактически представляет собой обёрткувокруг соответствующего объекта Array (он, как и раньше, называетсяarr).
Обёртка добавляет операцию индексирования и задаёт аргументконструктора:tem plate < c la s s Т, Т in it_ v a l>c l a s s M ultiM atrix<T , i n i t _ v a l , 1> {Array<T> a r r ;p u b l ic :M u ltiM atrix () : a r r ( i n i t _ v a l ) { }123Т& o p e ra to r [] (unsigned in t id x ) {re tu rn a r r .E le m ( id x ) ;}Полученный шаблон можно проверить, например, с помощью такойфункции m ain:in t m ain() {M u ltiM a trix < in t, - 1 , 5> mm;mm[3] [4] [5] [2] [7] = 193;mm[2] [2] [2] [2] [2] = 251;p rin tf("*/,d */,d */,d */,d\n",mm[3] [4] [5] [2] [7] , mm[2] [2] [2] [2] [2] ,mm[0] [1] [2] [3] [4] , mm[l] [2] [3] [2] [1] ) ;re tu rn 0;}Программа напечатает « 1 9 3 251 -1 -1 » .Целыми числами возможности параметров шаблонов не исчерпываются. Параметром шаблона может быть константа любого типа, известная на момент компиляции; в частности, можно сделать шаблон с параметром типа d o u b le и инстанциировать его для значения 2 .
5 + 0 .7 , непонятно только, зачем так делать. В качестве параметра шаблона могутвыступать и адресные выражения (указатели), но здесь действует целыйряд ограничений. Фактическим значением адресного параметра шаблонаможет быть только адрес глобальной переменной или функции2, причёмтолько в форме & var, &f или просто f , где v a r — имя глобальной переменной, f — имя функции. Недопустимо использовать сложные адресныевыражения, нельзя использовать адреса локальных переменных (что ипонятно, ведь их невозможно определить во время компиляции), нельзяиспользовать строковые литералы (строки в двойных кавычках — этоуже не столь понятно). Впрочем, в реальной жизни очень редко применяется что-либо кроме типов и целочисленных выражений.2Ещё можно использовать так называемый указатель на член класса, но в нашемпособии эта сущность не рассматривается.124Ч то д а л ь ш е (вместо послесловия)Это послесловие предназначено, пожалуй, только для тех, кто планирует стать профессиональным программистом.Несомненно, при приёме на работу в любую коммерческую организацию от вас на собеседовании потребуют знания STL.
Программированиена С и + + без STL возможно и, более того, позволяет получать более эффективные и качественные программы, но вам вряд ли удастся убедитьвашего работодателя (будущего начальника) отказаться от STL, поскольку он, скорее всего, относится к числу программистов, изучавших С и + +после 1999 года.Если такое собеседование предстоит вам в течение ближайших двухнедель, то у вас, очевидно, нет иного выхода, кроме как немедленно начинать изучение STL.