Программирование >>  Полиморфизм без виртуальных функций в с++ 

1 ... 114 115 116 [ 117 ] 118 119 120 ... 144


В шаблоне функции compare () для сравнения аргументов THnabasic string используется такая форма:

template,<class Т> class basic string { . . .

template<class Т, class С = СМР<Т> > int compare(const basic string<T>& stri, const basic string<T>& str2)

fordnt i=0; i<strl.lengthO && i<str2 . length (); i++) if (!C::eq(strl[i],str2[i]))

return C::lt(stri[i],str2[i]); return str2.1ength()-stri.length{);

typedef basic string<char> string;

Имея в своем распоряжении шаблоны-члены (см. раздел 15.9.3), функцию compare () можно было бы определить и в виде члена класса basic string.

Если требуется, чтобы С<Т> производил сравнение без учета регистра, 1Ю с учетом специфики конкретного языка, возвращал наибольшее значение в коде Unicode, когда аргументы неравны (имеется в виду С<Т>: : eq ()) и т.д., то нужно лишь правильным способом определить С<Т>: : eq О и С<Т>: : It () через характерные для типа Т операторы. Тогда любой алгоритм (сравнения, сортировки и т.п.) можно выразить в терминах операций, предоставляемых классо.м СМР и контейнером. Например:

class LITERATE {

static int eq(char a, char b) { return a==b; }

static int It(char,char); использовать книжный порядок

void f(string swedel, string swede2) {

compare(swedel,swede2); обычный (телефонный) порядок compare<char,LITERATE>(swedel,swede2); книжный порядок

Я передаю критерий сравнения в виде параметра шаблона, поскольку и.менно так можно передать операции без лишних затрат во время выполнения. В частности, операторы сравнения eq() и It () легко встроить. Аргумент по умолчанию используется, чтобы не обременять пользователей громоздкой нотацией. Другие варианты этой техники рассматриваются в [2nd, §8.4].

Более характерный пример - это сравнение с учетом и без учета регистра:

void f(string sl, string s2) {

compare(sl,s2); с учетом регистра

compare<char,NOCASE>(sl,s2); без учета регистра



Отметим, что шаблон класса СМР никогда не используется для определения объектов; все его члены статические и открытые. Поэтому его следовало бы сделать пространством имен (см. главу 17):

template<class Т> namespace СМР { int eq{T а, Т b) { return a==b; } int lt(T a, Т b) { return a<b; }

К сожалению, шаблоны-пространства имен (пока еше) не включены в С++.

15.9. Соотношения между шаблонами классов

Шаблон стоит рассматривать как спецификацию для создания конкретных типов. Другими словами, реализация шаблона - это механизм генерирования типов, указанных пользователем.

Согласно правилам языка С++ два класса, сгенерированные из одного и того же шаблона, никак не связаны между собой. Например:

template<class Т> class Set { /* ... */ };

class Shape ( /* ... */ } ;

class Circle : public Shape ( /* ... */ };

Видя подобные объявления, пользователи зачастую трактуют Set<Circle> как класс, производный от Set<Shape>, или Set<Circle*> - как производный от Set<Shape*>. Например:

void f(Set<Shape>&);

void g(Set<Circle& s) (

f (s);

Этот пример не будет компилироваться, поскольку не сушествует встроенного преобразования из Set<Circle>& в Set<Shape>&. Да и не должно его быть; полагать, что Set<Circle> - частный случай Set<Shape>, - принципиальная (и не такая уж редкая) концептуальная ошибка. В частности, класс Set<Circle> гарантирует, что все его элементы принадлежат circle (окружность), и значит, пользователи могут безопасно и эффективно применять к ним все операции, определенные для окружностей, например запрашивать значение радиуса. Если бы мы разрешили трактовать Set<Circle> как Set<Shape>, то уже не могли бы дать такой гарантии, поскольку в множество Set<Shape> можно поместить и другие геометрические фигуры, например, треугольники.

15.9.1. Отношения наследования

Следовательно, по умолчанию между классами, сгенерированными из одного и того же шаблона, не может быть никаких отношений. Но иногда такое отношение полезно. Нужна ли специальная операция для выражения такого рода отношений?



Я отверг эту идею, поскольку многие полезные преобразования можно выразить с помощью отнощений наследования или обычных операторов-конверторов. Однако в результате не существует способа выразить некоторые отношения. Например, располагая

template<class Т> class Ptr { указатель на Т ...

часто хотелось бы для таких определенных пользователем указателей Ptr иметь такие же отношения наследования, к которым мы привыкли при работе со встроенными указателями. Например:

void f{Ptr<Circle> pc) (

Ptr<Shape> ps = pc; имеет ли это смысл?

Желательно, чтобы это было разрешено только тогда, когда Shape действительно является непосредственным или косвенным открытым базовы.м классом для Circle. Дэвид Джордан (David Jordan) от имени консорциума поставщиков объектно-ориентированных баз данных просил ко.митет по стандартизации обеспечить такое свойство для умных указателей.

Решение дают шаблоны-члены, которые пока не включены в C-i-i-:

template<class Tl> class Ptr { указатель на Tl ...

template<class T2> operator Ptr<T2> ();

Нам нужно определить конвертор таким образом, чтобы преобразование Ptr<Tl> в Ptr<T2> было допустимо только в случаях, когда Т1* .можно присвоить Т2*. Это можно сделать, предоставив для ptr дополнительный конструктор:

template<class Т> class Ptr { указатель на Т

Т* р; public:

Ptr(T*);

template<class Т2> operator Ptr<T2> ();

return Ptr<T2>{p); работает только тогда, когда р можно преобразовать в Т2*

...

В этом решении не используются приведения типов. Предложение return будет компилироваться, только когда р может являться аргументом для конструктора Ptr<T2>. В приведенном примере р имеет тип Т1*, а конструктору необходим аргумент типа Т2 *. Это применение метода ограничения через использование (см. раздел 15.4.2). Если вы предпочитаете иметь закрытый конструктор, то можете воспользоваться приемом, предложенным Джонатаном Шопиро:



1 ... 114 115 116 [ 117 ] 118 119 120 ... 144

© 2006 - 2025 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика