На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
! Друзья, соблюдайте, пожалуйста, правила форума и данного раздела:
Данный раздел не предназначен для вопросов и обсуждений, он содержит FAQ-заготовки для разных языков программирования. Любой желающий может разместить здесь свою статью. Вопросы же задавайте в тематических разделах!
• Если ваша статья может быть перенесена в FAQ соответствующего раздела, при условии, что она будет оформлена в соответствии с Требованиями к оформлению статей.
• Чтобы остальным было проще понять, указывайте в описании темы (подзаголовке) название языка в [квадратных скобках]!
Модераторы: Модераторы
  
> Обобщения aka Generics., [Pascal] FPC >= 2.2.x
    Обобщения aka Generics.

    Разработчикам, использующим объектно-ориентированное программирование,
    хорошо известны его преимущества. Одно из ключевых преимуществ - возмож-
    ность повторно использовать код, т.е. создавать производный класс, нас-
    ледующий все возможности базового класса. В производном классе можно
    просто переопределить виртуальные методы или добавить новые, чтобы изме-
    нить унаследованные характеристики для решения новых задач. Обобщения
    (Generics)
    - еще один новый (начиная с версии 2.2.х) механизм повторного
    использования кода, а именно повторным использованием алгоритма.

    По сути, разработчик определяет алгоритм, например сортировку, поиск,
    замену, преобразование и т.д. но не указывает конкретный тип данных, с
    которым работает алгоритм. Именно поэтому алгоритм можно обобщенно при-
    менять к объектам разных типов. Используя готовый алгоритм, другой раз-
    работчик просто указывает конкретный тип, например для сортировки -
    Integer, String или даже Record и Class.

    В FPC обобщения реализованы как своего рода макросы для компилятора,
    которые он выполняет при специализации (specialize) т.е. при их
    непосредственном использовании при указании конкретного типа. Именно
    поэтому описание и использование обобщений происходит за два этапа:
    1. Описание обобщения по-сути описывает новый тип: макрос, который в
    последствии может выполнять компилятор.
    2. Специализация обобщения - создание нового специализированного
    класса из обобщения, путем исполнения компилятором макроса из прош-
    лого этапа.

    Рассмотрим, как же описываются обобщения в FPC на простом примере
    списка:

    ExpandedWrap disabled
      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;


    Ну и реализация методов:

    ExpandedWrap disabled
      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 не может быть использован ни для чего иного
    кроме шаблона т.е.

    ExpandedWrap disabled
      procedure GList.ForEach(p : TForEachProc);
      var
         i : integer;
        _t : integer; // ошибка!
      begin
        ...
      end { ForEach };


    2. Локальный блок описаний типов (в примере) содержит тип TForEachProc.
    Обратите внимание, конкретный тип неизвестен при описании обобщения:
    описание содержит ссылку на шаблон _T. Все другие ссылки на иденти-
    фикаторы должны быть известны при описании обобщения т.е. еще до
    специализации.
    3. Локальный блок переменных, введенный для удобства и повышения
    "читабельности" кода полностью эквивалентен:

    ExpandedWrap disabled
      private
        Arr : array of _T;        // В основе списка лежит динамический массив
        Len : integer;            // Длина массива
      public              // Область публичных методов
        function Add(item : _T): integer;
        ...


    4. Оба локальных блока типов и переменных могут имеют необязательный
    спецификатор видимости. При его отсутствии используется текущая
    видимость.

    Рассмотрим теперь специализацию обобщений.

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

    Специализация возможна только в блоках type и выглядит следующим образом:

    ExpandedWrap disabled
      type
        TGL_int = specialize GList<integer>;
        TGL_str = specialize GList<string>;


    Описание же переменных с использованием специализации запрещено:

    ExpandedWrap disabled
      var
        TGL_smpl : specialize GList<integer>; // Ошибка


    Кроме того, тип специализации (тот что в угловых скобках) должен быть
    известен. Рассмотрим пример:

    ExpandedWrap disabled
      type
        Generic TMyFirstType<T1> = Class(TMyObject);
        Generic TMySecondType<T2> = Class(TMyOtherObject);
      ...
      type
        TMySpecialType = specialize TMySecondType<TMyFirstType>; // Ошибка!


    Ошибка возникает потому, что тип TMyFirstType лишь обобщение а не
    полностью определенный тип. Однако, следующий трюк вполне работоспособен:

    ExpandedWrap disabled
      type
        TA = specialize TMyFirstType<Atype>;
        TB = specialize TMySecondType<TA>;


    потому что TA - полностью определенный, специализированный тип.

    Но стоит заметить, что две одинаковые специализации одного и того же
    шаблона нельзя присваивать друг другу что само собой вытекает из правил
    эквивалентности типов... Эти 2 типа просто не эквивалентны, только поэтому
    (Generic-и тут ни при чем) нельзя присваивать друг другу переменные раз-
    ных типов(спасибо volvo877). Например тут:

    ExpandedWrap disabled
      type
        TA = specialize GList<integer>;
        TB = specialize GList<integer>;
      var
        A : TA;
        B : TB;
      begin
        A := B; // Ошибка!


    присвоение В к А вызывает ошибку.

    Ну и в конце - пример использования:

    ExpandedWrap disabled
      {$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.


    И его результаты работы:

    ExpandedWrap disabled
      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 стр. : ил.


    Добавлено
    Ну что ж , жду критики.. :)
    Сообщение отредактировано: Jin X -

    Прикреплённый файлПрикреплённый файлGnrcLst.zip (1.17 Кбайт, скачиваний: 187)
    user posted image
      Цитата e-moe @
      Но стоит заметить, что две одинаковые специализации одного и того же
      шаблона нельзя присваивать друг другу.
      Что само собой вытекает из правил эквивалентности типов... Эти 2 типа просто не эквивалентны, только поэтому (Generic-и тут ни при чем) нельзя присваивать друг другу переменные разных типов. Как, например, и здесь:
      ExpandedWrap disabled
        type
          TA = array[1 .. 10] of integer;
          TB = array[1 .. 10] of integer;
         
        var
          A: TA;
          B: TB;
         
        begin
          A := B; // Здесь тоже ошибка ...
        end.

      А вообще-то пока нет поддержки Generic-ов в процедурах/функциях - их очень неудобно использовать...
        Цитата volvo877 @
        Что само собой вытекает из правил...

        Спасибо, подправил.

        Цитата volvo877 @
        А вообще-то пока нет поддержки Generic-ов в процедурах/функциях - их очень неудобно использовать...

        Не спорю.. Но начало то положено что не может не радовать :)

        Добавлено
        Вот только меня смущает терминология.. не знаю как лучше...
        Особенно настараживает то, как я использовал слово "шаблон" для обозначения того, что внутри угловых скобочек..
        Просто может возникнуть путаница с С++ным тимплэйтом...

        или все ок?
        user posted image
          В доках то, что в угловых скобочках названо spaceholder-ом... А вот как это по-русски назвать я пока не придумал :)
            Цитата volvo877 @
            А вот как это по-русски назвать я пока не придумал :)

            Ну если вдруг че придумаешь покрасивше - пиши ;)
            user posted image
              Добавил выводы...

              Добавлено
              Только не спрашивайте при чем тут книга по C# %)
              Там на самом деле много интересного, а при достаточной фантазии кое-что можно от туда смело применять в контексте FPC :)
              user posted image
              1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
              0 пользователей:


              Рейтинг@Mail.ru
              [ Script Execution time: 0,1319 ]   [ 16 queries used ]   [ Generated: 17.07.19, 18:38 GMT ]