Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.97.14.84] |
|
Сообщ.
#1
,
|
|
|
Обобщения aka Generics.
Разработчикам, использующим объектно-ориентированное программирование, хорошо известны его преимущества. Одно из ключевых преимуществ - возмож- ность повторно использовать код, т.е. создавать производный класс, нас- ледующий все возможности базового класса. В производном классе можно просто переопределить виртуальные методы или добавить новые, чтобы изме- нить унаследованные характеристики для решения новых задач. Обобщения (Generics) - еще один новый (начиная с версии 2.2.х) механизм повторного использования кода, а именно повторным использованием алгоритма. По сути, разработчик определяет алгоритм, например сортировку, поиск, замену, преобразование и т.д. но не указывает конкретный тип данных, с которым работает алгоритм. Именно поэтому алгоритм можно обобщенно при- менять к объектам разных типов. Используя готовый алгоритм, другой раз- работчик просто указывает конкретный тип, например для сортировки - Integer, String или даже Record и Class. В FPC обобщения реализованы как своего рода макросы для компилятора, которые он выполняет при специализации (specialize) т.е. при их непосредственном использовании при указании конкретного типа. Именно поэтому описание и использование обобщений происходит за два этапа: 1. Описание обобщения по-сути описывает новый тип: макрос, который в последствии может выполнять компилятор. 2. Специализация обобщения - создание нового специализированного класса из обобщения, путем исполнения компилятором макроса из прош- лого этапа. Рассмотрим, как же описываются обобщения в FPC на простом примере списка: type generic GList<_T> = class type public // Область типов (публичная) // Тип функции для метода ForEach TForEachProc = procedure(item: _T); var private // Область полей (приватная) Arr : array of _T; // В основе списка лежит динамический массив Len : integer; // Длина массива public // Область публичных методов function Add(item : _T): integer; procedure DeleteAt(p : integer); procedure ForEach(p : TForEachProc); procedure Clear; constructor Create; destructor Destroy; override; end; Ну и реализация методов: function GList.Add(item : _T): integer; begin SetLength(Arr,Len+1); Arr[Len] := item; Result := Len; inc(Len); end { Add }; procedure GList.DeleteAt(p : integer); var i : integer; begin if (p >= 0) and (p < Len) then begin for i := p to Len-2 do Arr[i] := Arr[i+1]; dec(Len); SetLength(Arr,Len); end; end { DeleteAt }; procedure GList.ForEach(p : TForEachProc); var i : integer; begin for i:= Low(Arr) to High(Arr) do p(Arr[i]); end { ForEach }; procedure GList.Clear; begin Arr := nil; Len := 0; end { Clear }; constructor GList.Create; begin inherited; Len := 0; end { Create }; destructor GList.Destroy; begin Clear; inherited; end { Destroy }; Как видно из примера, описание обобщений очень похоже не описание обычного класса за исключением локальных блоков описаний типов и пере- менных как в модулях или подпрограммах. Рассмотрим некоторые особенности описания и реализации: 1. Тип _T своего рода шаблон, вместо которого на этапе специализации будет подставлен конкретный тип, заранее неизвестный. Кроме того, идентификатор _T не может быть использован ни для чего иного кроме шаблона т.е. procedure GList.ForEach(p : TForEachProc); var i : integer; _t : integer; // ошибка! begin ... end { ForEach }; 2. Локальный блок описаний типов (в примере) содержит тип TForEachProc. Обратите внимание, конкретный тип неизвестен при описании обобщения: описание содержит ссылку на шаблон _T. Все другие ссылки на иденти- фикаторы должны быть известны при описании обобщения т.е. еще до специализации. 3. Локальный блок переменных, введенный для удобства и повышения "читабельности" кода полностью эквивалентен: private Arr : array of _T; // В основе списка лежит динамический массив Len : integer; // Длина массива public // Область публичных методов function Add(item : _T): integer; ... 4. Оба локальных блока типов и переменных могут имеют необязательный спецификатор видимости. При его отсутствии используется текущая видимость. Рассмотрим теперь специализацию обобщений. Однажды описанное обобщение может быть использовано для генерации других классов: это похоже на повторение описания класса только уже с шаблонами, указывающими на конкретные типы данных. Специализация возможна только в блоках type и выглядит следующим образом: type TGL_int = specialize GList<integer>; TGL_str = specialize GList<string>; Описание же переменных с использованием специализации запрещено: var TGL_smpl : specialize GList<integer>; // Ошибка Кроме того, тип специализации (тот что в угловых скобках) должен быть известен. Рассмотрим пример: type Generic TMyFirstType<T1> = Class(TMyObject); Generic TMySecondType<T2> = Class(TMyOtherObject); ... type TMySpecialType = specialize TMySecondType<TMyFirstType>; // Ошибка! Ошибка возникает потому, что тип TMyFirstType лишь обобщение а не полностью определенный тип. Однако, следующий трюк вполне работоспособен: type TA = specialize TMyFirstType<Atype>; TB = specialize TMySecondType<TA>; потому что TA - полностью определенный, специализированный тип. Но стоит заметить, что две одинаковые специализации одного и того же шаблона нельзя присваивать друг другу что само собой вытекает из правил эквивалентности типов... Эти 2 типа просто не эквивалентны, только поэтому (Generic-и тут ни при чем) нельзя присваивать друг другу переменные раз- ных типов(спасибо volvo877). Например тут: type TA = specialize GList<integer>; TB = specialize GList<integer>; var A : TA; B : TB; begin A := B; // Ошибка! присвоение В к А вызывает ошибку. Ну и в конце - пример использования: {$mode objfpc} uses GnrcLst; type TGL_int = specialize GList<integer>; TGL_str = specialize GList<string>; var l1 : TGL_int; l2 : TGL_str; procedure ForEach_int(item : integer); begin WriteLn(item) end { ForEach_int }; procedure ForEach_str(item : string); begin WriteLn(item) end { ForEach_int }; begin l1 := TGL_int.Create; l1.Add(3); l1.Add(7); l1.Add(15); Writeln('Список integer''ов:'); l1.ForEach(@ForEach_int); l1.DeleteAt(1); Writeln('Список integer''ов после удаления 1го элемента:'); l1.ForEach(@ForEach_int); l1.Free; WriteLn; l2 := TGL_str.Create; l2.Add('1th'); l2.Add('2th'); l2.Add('3th'); Writeln('Список string''ов:'); l2.ForEach(@ForEach_str); l2.DeleteAt(1); Writeln('Список string''ов после удаления 1го элемента:'); l2.ForEach(@ForEach_str); l2.Free; end. И его результаты работы: Running "d:\pp\work\t_gnrclst.exe " Список integer'ов: 3 7 15 Список integer'ов после удаления 1го элемента: 3 15 Список string'ов: 1th 2th 3th Список string'ов после удаления 1го элемента: 1th 3th Послесловие или что все это дает?.. Перечислю пару плюсов минусов обобщений: + Безопасность типов. Когда обобщенный алгоритм специализируется компилятор понимает это и не допускает работу с другими типами. Так, вы не сможете в GList<MyClass1> добавить элемент типа MyClass2 несмотря на то что у них есть общий родитель TObject чего не скажешь о стандартном классе TList, который работает с указателями. + Более простой и понятный код. Поскольку компилятор обеспечивает безопас- ность типов, в исходном коде нужно меньше привидений типов. И как следствие, такой код проще писать и поддерживать. - "Распухание" кода. Компилятор будет генерировать машинный код для каждого сочетания "обобщение + специализация" что в итоге может привести к увеличению размера приложения. - Новизна. В FPC обобщения только только появляются и многие возможности пока еще не реализованы. К ним относится и отсутствие поддержки Generic-ов в процедурах/функциях что привносит некоторые неудобства... p.s. Все исходники можно найти в аттаче. 2007 © Nikolay Labinskiy aka e-moe При написании использовались: * Оригинальная документация к FPC * CLR via C#. Программирование на платформе Microsoft .NET Framework 2.0 на языке C#. Мастер-класс. / Пер. с англ. - М.: Издательство "РУсская редакция"; СПб.: Питер, 2007. - 656 стр. : ил. Добавлено Ну что ж , жду критики.. Прикреплённый файлGnrcLst.zip (1.17 Кбайт, скачиваний: 306) |
Сообщ.
#2
,
|
|
|
Цитата e-moe @ Что само собой вытекает из правил эквивалентности типов... Эти 2 типа просто не эквивалентны, только поэтому (Generic-и тут ни при чем) нельзя присваивать друг другу переменные разных типов. Как, например, и здесь:Но стоит заметить, что две одинаковые специализации одного и того же шаблона нельзя присваивать друг другу. type TA = array[1 .. 10] of integer; TB = array[1 .. 10] of integer; var A: TA; B: TB; begin A := B; // Здесь тоже ошибка ... end. А вообще-то пока нет поддержки Generic-ов в процедурах/функциях - их очень неудобно использовать... |
Сообщ.
#3
,
|
|
|
Цитата volvo877 @ Что само собой вытекает из правил... Спасибо, подправил. Цитата volvo877 @ А вообще-то пока нет поддержки Generic-ов в процедурах/функциях - их очень неудобно использовать... Не спорю.. Но начало то положено что не может не радовать Добавлено Вот только меня смущает терминология.. не знаю как лучше... Особенно настараживает то, как я использовал слово "шаблон" для обозначения того, что внутри угловых скобочек.. Просто может возникнуть путаница с С++ным тимплэйтом... или все ок? |
Сообщ.
#4
,
|
|
|
В доках то, что в угловых скобочках названо spaceholder-ом... А вот как это по-русски назвать я пока не придумал
|
Сообщ.
#5
,
|
|
|
Цитата volvo877 @ А вот как это по-русски назвать я пока не придумал Ну если вдруг че придумаешь покрасивше - пиши |
Сообщ.
#6
,
|
|
|
Добавил выводы...
Добавлено Только не спрашивайте при чем тут книга по C# %) Там на самом деле много интересного, а при достаточной фантазии кое-что можно от туда смело применять в контексте FPC |