straustrup2 (852740), страница 15
Текст из файла (страница 15)
В нашемпримере шаблон типа Vector показывает, как можно получить класс вектор для заданного типа егоэлементов. Это описание отличается от обычного описания класса наличием начальной конструкцииtemplate<class T>, которая и показывает, что описывается не класс, а шаблон типа с заданнымпараметром-типом (здесь он используется как тип элементов).Теперь можно определять ииспользовать вектора разных типов:void f (){Vector < int > v1 ( 100 );Vector < complex > v2 ( 200 );// вектор из 100 целых// вектор из 200// комплексных чиселv2 [ i ] = complex ( v1 [ x ], v1 [ y ] );// ...}Возможности, которые реализует шаблон типа, иногда называются параметрическими типами илигенерическими объектами.Оно сходно с возможностями, имеющимися в языках Clu и Ада.Использование шаблона типа не влечет за собой каких-либо дополнительных расходов времени посравнению с использованием класса, в котором все типы указаны непосредственно.1.4.4 Обработка особых ситуацийПо мере роста программ, а особенно при активном использовании библиотек появляетсянеобходимость стандартной обработки ошибок (или, в более широком смысле, "особых ситуаций").Языки Ада, Алгол-68 и Clu поддерживают стандартный способ обработки особых ситуаций.Снова вернемся к классу vector.
Что нужно делать, когда операции индексации передано значениеиндекса, выходящее за границы массива? Создатель класса vector не знает, на что рассчитываетпользователь в таком случае, а пользователь не может обнаружить подобную ошибку (если бы мог, то37Бьерн Страуструп.Язык программирования С++эта ошибка вообще не возникла бы). Выход такой: создатель класса обнаруживает ошибку выхода заграницу массива, но только сообщает о ней неизвестному пользователю. Пользователь сам принимаетнеобходимые меры.Например:class vector {// определение типа возможных особых ситуацийclass range { };// ...};Вместо вызова функции ошибки в функции vector::operator[]() можно перейти на ту часть программы, вкоторой обрабатываются особые ситуации.
Это называется "запустить особую ситуацию" ("throw theexception"):int & vector::operator [] ( int i ){if ( i < 0 || sz <= i ) throw range ();return v [ i ];}В результате из стека будет выбираться информация, помещаемая туда при вызовах функций, до техпор, пока не будет обнаружен обработчик особой ситуации с типом range для класса вектор(vector::range); он и будет выполняться.Обработчик особых ситуаций можно определить только для специального блока:void f ( int i ){try{// в этом блоке обрабатываются особые ситуации// с помощью определенного ниже обработчикаvector v ( i );// ...v [ i + 1 ] = 7;// приводит к особой ситуации range// ...g (); // может привести к особой ситуации range// на некоторых векторах}catch ( vector::range ){error ( "f (): vector range error" );return;}}Использование особых ситуаций делает обработку ошибок более упорядоченной и понятной.Обсуждение и подробности отложим до главы 9.1.4.5 Преобразования типовОпределяемые пользователем преобразования типа, например, такие, как преобразование числа сплавающей точкой в комплексное, которое необходимо для конструктора complex(double), оказалисьочень полезными в С++.
Программист может задавать эти преобразования явно, а может полагаться натранслятор, который выполняет их неявно в том случае, когда они необходимы и однозначны:complexcomplexa = b +a = b +a = complex ( 1 );b = 1;complex ( 2 );2;// неявно: 1 -> complex ( 1 )// неявно: 2 -> complex ( 2)38Бьерн Страуструп.Язык программирования С++Преобразования типов нужны в С++ потому, что арифметические операции со смешанными типамиявляются нормой для языков, используемых в числовых задачах. Кроме того, большая частьпользовательских типов, используемых для "вычислений" (например, матрицы, строки, машинныеадреса) допускает естественное преобразование в другие типы (или из других типов).Преобразования типов способствуют более естественной записи программы:complex a = 2;complex b = a + 2;b = 2 + a;// это означает: operator + ( a, complex ( 2 ))// это означает: operator + ( complex ( 2 ), a )В обоих случаях для выполнения операции "+" нужна только одна функция, а ее параметрыединообразно трактуются системой типов языка.
Более того, класс complex описывается так, что дляестественного и беспрепятственного обобщения понятия числа нет необходимости что-то изменять дляцелых чисел.1.4.6 Множественные реализацииОсновные средства, поддерживающие объектно-ориентированное программирование, а именно:производные классы и виртуальные функции,- можно использовать и для поддержки абстракцииданных, если допустить несколько реализаций одного типа. Вернемся к примеру со стеком:template < class T >class stack{public:virtual void push ( T ) = 0;virtual T pop () = 0;};// чистая виртуальная функция// чистая виртуальная функцияОбозначение =0 показывает, что для виртуальной функции не требуется никакого определения, а классstack является абстрактным, т.е. он может использоваться только как базовый класс. Поэтому стекиможно использовать, но не создавать:class cat { /* ... */ };stack < cat > s;// ошибка: стек - абстрактный классvoid some_function ( stack <cat> & s, cat kitty ) // нормально{s.push ( kitty );cat c2 = s.pop ();// ...}Поскольку интерфейс стека ничего не сообщает о его представлении, от пользователей стекаполностью скрыты детали его реализации.Можно предложить несколько различных реализаций стека.
Например, стек может быть массивом:template < class T >class astack : public stack < T >{// истинное представление объекта типа стек// в данном случае - это массив// ...public:astack ( int size );~astack ();void push ( T );T pop ();};Можно реализовать стек как связанный список:39Бьерн Страуструп.Язык программирования С++template < class T >class lstack : public stack < T >{// ...};Теперь можно создавать и использовать стеки:void g (){lstack < cat > s1 (astack < cat > s2 (cat Ginger;cat Snowball;some_function ( s1,some_function ( s2,}100 );100 );Ginger );Snowball );О том, как представлять стеки разных видов, должен беспокоиться только тот, кто их создает (т.е.функция g()), а пользователь стека (т.е. автор функции some_function()) полностью огражден от деталейих реализации.
Платой за подобную гибкость является то, что все операции над стеками должны бытьвиртуальными функциями.1.5 Поддержка объектно-ориентированного программированияПоддержку объектно-ориентированного программирования обеспечивают классы вместе с механизмомнаследования, а также механизм вызова функций-членов в зависимости от истинного типа объекта(дело в том, что возможны случаи, когда этот тип неизвестен на стадии трансляции). Особенно важнуюроль играет механизм вызова функций-членов.Не менее важны средства, поддерживающиеабстракцию данных (о них мы говорили ранее). Все доводы в пользу абстракции данных ибазирующихся на ней методов, которые позволяют естественно и красиво работать с типами,действуют и для языка, поддерживающего объектно-ориентированное программирование.
Успех обоихметодов зависит от способа построения типов, от того, насколько они просты, гибки и эффективны.Метод объектно-ориентированного программирования позволяет определять более общие и гибкиепользовательские типы по сравнению с теми, которые получаются, если использовать толькоабстракцию данных.1.5.1 Механизм вызоваОсновное средство поддержки объектно-ориентированного программирования - это механизм вызовафункции-члена для данного объекта, когда истинный тип его на стадии трансляции неизвестен.
Пусть,например, есть указатель p. Как происходит вызов p->rotate(45)? Поскольку С++ базируется настатическом контроле типов, задающее вызов выражение имеет смысл только при условии, чтофункция rotate() уже была описана. Далее, из обозначения p->rotate() мы видим, что p являетсяуказателем на объект некоторого класса, а rotate должна быть членом этого класса. Как и при всякомстатическом контроле типов проверка корректности вызова нужна для того, чтобы убедиться (насколькоэто возможно на стадии трансляции), что типы в программе используются непротиворечивым образом.Тем самым гарантируется, что программа свободна от многих видов ошибок.Итак, транслятору должно быть известно описание класса, аналогичное тем, что приводились в $$1.2.5:class shape{// ...public:// ...virtual void rotate ( int );// ...};а указатель p должен быть описан, например, так:40Бьерн Страуструп.Язык программирования С++T * p;где T - класс shape или производный от него класс.
Тогда транслятор видит, что класс объекта, накоторый настроен указатель p, действительно имеет функцию rotate(), а функция имеет параметр типаint. Значит, p->rotate(45) корректное выражение.Поскольку shape::rotate() была описана как виртуальная функция, нужно использовать механизм вызовавиртуальной функции. Чтобы узнать, какую именно из функций rotate следует вызвать, нужно до вызоваполучить из объекта некоторую служебную информацию, которая была помещена туда при егосоздании. Как только установлено, какую функцию надо вызвать, допустим circle::rotate, происходит еевызов с уже упоминавшимся контролем типа. Обычно в качестве служебной информации используетсятаблица адресов функций, а транслятор преобразует имя rotate в индекс этой таблицы.