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

  • 16.2.1. Аргументы шаблона для параметров-констант

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


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница75 из 93
    1   ...   71   72   73   74   75   76   77   78   ...   93

    783
    #endif
    При использовании имени Queue внутри определения шаблона класса Queue список параметров можно опускать. Однако пропуск списка параметров шаблона
    QueueItem в определении шаблона Queue недопустим. Так, объявление члена front является ошибкой:
    }
    Упражнение 16.1
    Найдите ошибочные объявления (или пары объявлений) шаблонов классов: class Container1; class Container2;
    #ifndef QUEUE_H
    #define QUEUE_H
    // объявление QueueItem template class QueueItem; template class Queue { public:
    Queue() : front( 0 ), back ( 0 ) { }

    Queue();
    Type& remove(); void add( const Type & ); bool is_empty() const { return front == 0;
    } private:
    QueueItem *front;
    QueueItem *back;
    }; template class Queue { public:
    // ... private:
    // ошибка: список параметров для QueueItem неизвестен
    QueueItem *front;
    (a) template class Container1; template
    (b) template

    С++ для начинающих
    784
    class Container3 {}; class Container4 {}; class Container5; class Container6;
    Упражнение 16.2
    Следующее определение шаблона List некорректно. Как исправить ошибку?
    (c) template
    (d) template
    (e) template
    (f) template class Container6; template , int v> template class ListItem; template class List { public:
    List()
    : _at_front( 0 ), _at_end( 0 ), _current( 0 ), _size( 0 )
    {}
    List( const List & );
    List& operator=( const List & );
    List(); void insert( ListItem *ptr, elemType value ); int remove( elemType value );
    ListItem *find( elemType value ); void display( ostream &os = cout ); int size() { return _size; } private:
    ListItem *_at_front;
    ListItem *_at_end;
    ListItem *_current; int _size

    С++ для начинающих
    785
    };
    16.2.
    Конкретизация шаблона класса
    В определении шаблона указывается, как следует строить индивидуальные классы, если заданы один или более фактических типов или значений. По шаблону Queue автоматически генерируются экземпляры классов Queue с разными типами элементов.
    Например, если написать:
    Queue qi; то из обобщенного определения шаблона автоматически создается класс Queue для объектов типа int.
    Генерация конкретного класса из обобщенного определения шаблона называется
    конкретизацией шаблона. При такой конкретизации Queue для объектов типа int каждое вхождение параметра Type в определении шаблона заменяется на int, так что определение класса Queue принимает вид:
    };
    Чтобы создать класс Queue для объектов типа string, надо написать:
    Queue qs;
    При этом каждое вхождение Type в определении шаблона будет заменено на string.
    Объекты qi и qs являются объектами автоматически созданных классов.
    Каждый конкретизированный по одному и тому же шаблону экземпляр класса совершенно не зависит от всех остальных. Так, у Queue для типа int нет никаких прав доступа к неоткрытым членам того же класса для типа string.
    Конкретизированный экземпляр шаблона будет иметь соответственно имя Queue или Queue. Части и , следующие за именем Queue, называются фактическими аргументами шаблона. Они должны быть заключены в угловые скобки и отделяться друг от друга запятыми. В имени конкретизируемого шаблона аргументы всегда должны задаваться явно. В отличие от аргументов шаблона функции, аргументы шаблона класса никогда не выводятся из контекста: template class Queue { public:
    Queue() : front( 0 ), back ( 0 ) { }
    Queue(); int& remove(); void add( const int & ); bool is_empty() const { return front == 0;
    } private:
    QueueItem *front;
    QueueItem *back;

    С++ для начинающих
    786
    Queue qs; // ошибка: как конкретизируется шаблон?
    Конкретизированный шаблон класса Queue можно использовать в программе всюду, где допустимо употребление типа обычного класса:
    Queue *pqc = static_cast< Queue* > ( 0 );
    Объекты типа класса, конкретизированного по шаблону Queue, объявляются и используются так же, как объекты обычных классов:
    }
    В объявлении и определении шаблона можно ссылаться как на сам шаблон, так и на конкретизированный по нему класс:
    )
    Однако вне такого определения употребляются только конкретизированные экземпляры.
    Например, в теле обычной функции всегда надо задавать фактические аргументы шаблона Queue:
    // типы возвращаемого значения и обоих параметров конкретизированы из
    // шаблона класса Queue extern Queue< complex > foo( Queue< complex > &, Queue< complex > & );
    // указатель на функцию-член класса, конкретизированного из шаблона Queue bool (Queue::*pmf)() = 0;
    // явное приведение 0 к указателю на экземпляр Queue extern Queue eqd;
    Queue *pqi = new Queue;
    Queue aqi[1024]; int main() { int ix; if ( ! pqi->is_empty() ) ix = pqi->remove();
    // ... for ( ix = 0; ix < 1024; ++ix ) eqd[ ix ].add( ix );
    // ...
    // объявление шаблона функции template void bar( Queue &, // ссылается на обобщенный шаблон
    Queue & // ссылается на конкретизированный шаблон void foo( Queue &qi )
    {
    Queue *pq = &qi;
    // ...

    С++ для начинающих
    787
    }
    Шаблон класса конкретизируется только тогда, когда имя полученного экземпляра употребляется в контексте, где требуется определение шаблона. Не всегда определение класса должно быть известно. Например, перед объявлением указателей и ссылок на класс его знать необязательно: void inverse( Matrix & ); // тоже правильно
    Поэтому объявление указателей и ссылок на конкретизированный шаблон класса не приводит к его конкретизации. (Отметим, что в некоторых компиляторах, написанных до принятия стандарта C++, шаблон конкретизируется при первом упоминании имени конкретизированного класса в тексте программы.) Так, в функции foo() объявляются указатель и ссылка на Queue, но это не вызывает конкретизации шаблона Queue:
    }
    Определение класса необходимо знать, когда определяется объект этого типа. В следующем примере определение obj1 ошибочно: чтобы выделить для него память, компилятору необходимо знать размер класса Matrix:
    Matrix obj2; // правильно
    Таким образом, конкретизация происходит тогда, когда определяется объект класса, конкретизированного по этому шаблону. В следующем примере определение объекта qi приводит к конкретизации шаблона Queue:
    Queue qi; // конкретизируется Queue
    Определение Queue становится известно компилятору именно в этой точке, которая называется точкой конкретизации данного класса.
    Если имеется указатель или ссылка на конкретизированный шаблон, то конкретизация также производится в момент обращения к объекту, на который они ссылаются. В определенной выше функции foo() класс Queue конкретизируется в следующих случаях: когда разыменовывается указатель pqi, когда ссылка qi используется для class Matrix;
    Matrix *pm; // правильно: определение класса Matrix знать необязательно
    // Queue не конкретизируется при таком использовании в foo() void foo( Queue &qi )
    {
    Queue *pqi = &qi;
    // ... class Matrix;
    Matrix obj1; // ошибка: класс Matrix не определен class Matrix { ... };

    С++ для начинающих
    788
    получения значения именуемого объекта и когда pqi или qi употребляются для доступа к членам или функциям-членам этого класса:
    }
    Определение Queue становится известным компилятору еще до вызова функции- члена add() из foo().
    Напомним, что в определении шаблона класса Queue есть также ссылка на шаблон
    QueueItem
    :
    };
    При конкретизации Queue типом int члены front и back становятся указателями на
    QueueItem
    . Следовательно, конкретизированный экземпляр Queue ссылается на экземпляр QueueItem, конкретизированный типом int. Но поскольку соответствующие члены являются указателями, то QueueItem конкретизируется лишь в момент их разыменования в функциях-членах класса Queue.
    Наш класс QueueItem служит вспомогательным средством для реализации класса Queue и не будет непосредственно употребляться в вызывающей программе. Поэтому пользовательская программа способна манипулировать только объектами Queue.
    Конкретизация шаблона QueueItem происходит лишь в момент конкретизации шаблона класса Queue или его членов. (В следующих разделах мы рассмотрим конкретизации членов шаблона класса.)
    В зависимости от типов, которыми может конкретизироваться шаблон, при его определении надо учитывать некоторые нюансы. Почему, например, следующее определение конструктора класса QueueItem не подходит для конкретизации общего вида?
    }; void foo( Queue &qi )
    {
    Queue *pqi = &qi;
    // Queue конкретизируется в результате вызова функции-члена pqi->add( 255 );
    // ... template class Queue { public:
    // ... private:
    QueueItem *front;
    QueueItem *back; template class QueueItem { public:
    QueueItem( Type ); // неудачное проектное решение
    // ...

    С++ для начинающих
    789
    В данном определении аргумент передается по значению. Это допустимо, если
    QueueItem конкретизируется встроенным типом (например, QueueItem). Но если такая конкретизация производится для объемного типа (скажем, Matrix), то накладные расходы, вызванные неправильным выбором на этапе проектирования, становятся неприемлемыми. (В разделе 7.3 обсуждались вопросы производительности, связанные с передачей параметров по значению и по ссылке.) Поэтому аргумент конструктора объявляется как ссылка на константный тип:
    QueueItem( const Type & );
    Следующее определение приемлемо, если у типа, для которого конкретизируется
    QueueItem
    , нет ассоциированного конструктора:
    };
    Если аргументом шаблона является тип класса с конструктором (например, string), то item инициализируется дважды! Конструктор по умолчанию string вызывается для инициализации item перед выполнением тела конструктора QueueItem. Затем для созданного объекта item производится почленное присваивание. Избежать такого можно с помощью явной инициализации item в списке инициализации членов внутри определения конструктора QueueItem:
    };
    (Списки инициализации членов и основания для их применения обсуждались в разделе
    14.5.)
    16.2.1.
    Аргументы шаблона для параметров-констант
    Параметр шаблона класса может и не быть типом. На аргументы, подставляемые вместо таких параметров, накладываются некоторые ограничения. В следующем примере мы изменяем определение класса Screen (см. главу 13) на шаблон, параметризованный высотой и шириной: template class QueueItem {
    // ... public:
    // потенциально неэффективно
    QueueItem( const Type &t ) { item = t; next = 0;
    } template class QueueItem {
    // ... public:
    // item инициализируется в списке инициализации членов конструктора
    QueueItem( const Type &t )
    : item(t) { next = 0; }

    С++ для начинающих
    790
    Screen<8,24> ancientScreen;
    Выражение, с которым связан параметр, не являющийся типом, должно быть константным, т.е. вычисляемым во время компиляции. В примере выше typedef termScreen ссылается на экземпляр шаблона Screen<24,80>, где аргумент шаблона для hi равен 24, а для wid – 80. В обоих случаях аргумент – это константное выражение.
    Однако для шаблона BufPtr конкретизация приводит к ошибке, так как значение указателя, получающееся при вызове оператора new(), становится известно только во время выполнения:
    BufPtr< new int[24] > bp;
    Не является константным выражением и значение неконстантного объекта. Его нельзя использовать в качестве аргумента для параметра-константы шаблона. Однако адрес любого объекта в области видимости пространства имен, в отличие от адреса локального объекта, является константным выражением (даже если спецификатор const отсутствует), поэтому его можно применять в качестве аргумента для параметра- константы. Константным выражением будет и значение оператора sizeof:
    Buf< size_val > buf3; template class Screen { public:
    Screen() : _height( hi ), _width( wid ), _cursor ( 0 ),
    _screen( hi * wid, '#' )
    { }
    // ... private: string _screen; string::size_type _cursor; short _height; short _width;
    }; typedef Screen<24,80> termScreen; termScreen hp2621; template class BufPtr { ... };
    // ошибка: аргумент шаблона нельзя вычислить во время компиляции template Buf { ... }; template class BufPtr { ... }; int size_val = 1024; const int c_size_val = 1024;
    Buf< 1024 > buf0; // правильно
    Buf< c_size_val > buf1; // правильно
    Buf< sizeof(size_val) > buf2; // правильно: sizeof(int)
    BufPtr< &size_val > bp0; // правильно
    // ошибка: нельзя вычислить во время компиляции

    С++ для начинающих
    791
    Вот еще один пример, иллюстрирующий использование параметра-константы для представления константного значения в определении шаблона, а также применение его аргумента для задания значения этого параметра:
    FixedArray< int, sizeof( is ) / sizeof( int ) > iA{ ia );
    Выражения с одинаковыми значениями считаются эквивалентными аргументами для параметров-констант шаблона. Так, все три экземпляра Screen ссылаются на один и тот же конкретизированный из шаблона класс Screen<24,80>:
    Screen< width, height > scr2;
    Между типом аргумента шаблона и типом параметра-константы допустимы некоторые преобразования. Их множество является подмножеством преобразований, допустимых для аргументов функции:

    трансформации l-значений, включающие преобразование l-значения в r- значение, массива в указатель и функции в указатель:
    BufPtr< array > bpObj; // преобразование массива в указатель

    преобразования квалификаторов: template < class Type, int size > class FixedArray { public:
    FixedArray( Type *ar ) : count( size )
    { for ( int ix = 0; ix < size; ++ix ) array[ ix ] = ar[ ix ];
    } private:
    Type array[ size ]; int count;
    }; int ia[4] = { 0, 1, 2, 3 }; const int width = 24; const int height = 80;
    // все это Screen< 24, 80 >
    Screen< 2*12, 40*2 > scr0;
    Screen< 6+6+6+6, 20*2 + 40 > scr1; template class BufPtr { ... }; int array[10]; template class Ptr { ... }; int iObj;

    С++ для начинающих
    792
    Ptr< &iObj > pObj; // преобразование из int* в const int*

    расширения типов:
    Screen< shi, swi > bpObj2; // расширения типа short до int

    преобразования целых типов:
    Buf< 1024 > bpObj; // преобразование из int в unsigned int
    (Более подробно они описаны в разделе 9.3.)
    Рассмотрим следующие объявления:
    Array a5; // ошибка: foo != PFV
    Объекты a0 и a4 класса Array определены правильно, так как аргументы шаблона точно соответствуют типам параметров. Объект a2 также определен правильно, потому что аргумент 1024 типа int приводится к типу unsigned int параметра-константы size с помощью преобразования целых типов. Объявления a1, a3 и a5 ошибочны, так как не существует преобразования между любыми двумя типами функций.
    Приведение значения 0 целого типа к типу указателя недопустимо: template class Screen { ... }; const short shi = 40; const short swi = 132; template Buf{ ... }; extern void foo( char * ); extern void bar( void * ); typedef void (*PFV)( void * ); const unsigned int x = 1024; template PFV handler> class Array { ... };
    Array a0; // правильно: преобразование не нужно
    Array a1; // ошибка: foo != PFV
    Array a2; // правильно: 1024 преобразуется в unsigned int
    Array a3; // ошибка: foo != PFV
    Array a4; // правильно: преобразование не нужно

    С++ для начинающих
    793
    BufPtr< 0 > nil;
    Упражнение 16.3
    Укажите, какие из данных конкретизированных шаблонов действительно приводят к конкретизации:
    }
    Упражнение 16.4
    Какие из следующих конкретизаций шаблонов корректны? Почему? template < int hi, int wid > class Screen { ... };
    Ptr< &size > bp1;
    (c) Ptr < 0 > bp3; template class BufPtr { ... };
    // ошибка: 0 имеет тип int
    // неявное преобразование в нулевой указатель не применяется template < class Type > class Stack { }; void f1( Stack< char > ); // (a) class Exercise {
    // ...
    Stack< double > &rsd; // (b)
    Stack< int > si; // (c)
    }; int main() {
    Stack< char > *sc; // (d) f1( *sc ); // (e) int iObj = sizeof( Stack< string > ); // (f) template < int *ptr > class Ptr ( ... }; template < class Type, int size > class Fixed_Array { ... };
    (a) const int size = 1024;
    (b) int arr[10];
    Ptr< arr > bp2;

    С++ для начинающих
    794
    Screen< hi, wi+32 > sObj;
    Fixed_Array< string, size_val > fa1;
    Fixed_Array< int, fasize > fa2;
    Fixed_Array< double, db > fa3;
    16.3.
    Функции-члены шаблонов классов
    Как и для обычных классов, функция-член шаблона класса может быть определена либо внутри определения шаблона (и тогда называется встроенной), либо вне его. Мы уже встречались со встроенными функциями-членами при рассмотрении шаблона Queue.
    Например, конструктор Queue является встроенным, так как определен внутри определения шаблона класса:
    };
    При определении функции-члена шаблона вне определения самого шаблона следует применять специальный синтаксис для обозначения того, членом какого именно шаблона является функция. Определению функции-члена должно предшествовать ключевое слово template
    , за которым следуют параметры шаблона. Так, конструктор Queue можно определить следующим образом:
    (d) const int hi = 40; const int wi = 80;
    (e) const int size_val = 1024;
    (f) unsigned int fasize = 255;
    (g) const double db = 3.1415; template class Queue {
    // ... public:
    // встроенный конструктор
    Queue() : front( 0 ), back( 0 ) { }
    // ...

    С++ для начинающих
    1   ...   71   72   73   74   75   76   77   78   ...   93


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