Наследование шаблонов

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Re: Наследование шаблонов

Сообщение Astralis » 03.01.2010 21:04:58

все из-за праздников так и не удавалось высказать свою мысль по поводу необходимости шаблонов, но моя идея оказалась частично раскрыта предыдущим постом. Очень часто в программе есть необходимость хранить структурированные данные в виде коллекции. Стандартный способ – определить потомка TCollectionItem и потом уже в программном коде создать
Код: Выделить всё
MyCollection := TCollection.Create(TMyCollectionItem);

Этот способ хорош, если коллекция используется для внутреннего использования. Если же она делается для интерфейса работы, то никто не застрахован, чтобы в этой коллекции попытаться сохранить элемент типа TStrangeCollectionItem и компилятор ошибки не выдаст, а в момент добавления возникнет runtime-ошибка InvalidProperty, которую отловить гораздо сложнее, особенно тому, кто не сам создавал данную коллекцию.
Поэтому создание коллекции сопровождается добавлением следующего в прямом смысле слова шаблонно-генерируемого кода
Код: Выделить всё
TMyCollection = class (TCollection)
public
    constructor Create;
    function Add: TMyCollectionItem;override;
    function FindItemID(ID: Integer): TMyCollectionItem;override;
    function Insert(Index: Integer): TMyCollectionItem;override;
    property ItemClass: TMyCollectionItemClass;override;
    property Items[Index: Integer]: TMyCollectionItem;override;
end;


По большому счету только в этом и все преимущество генериков. А теперь пойдем по недостаткам.
1) как правильно сделать связанные генерики? Например хочу, чтобы и базовый класс мог бы генерироваться так:

Код: Выделить всё
TMyCollectionItem = class(TCollectionItem)
public
    property Collection: TMyCollection;
end;


очень часто в C++ есть контейнеры и итераторы, между которыми аналогичная связь
2)мое лично мнение, но генерики должны быть только для классов.
то есть, например коллекция может генерироваться лишь от потомка TCollectionItem
Код: Выделить всё
TGenericCollection<T: TCollectionItem> = class

На данный момент никто не запрещает специализировать генерик чем-угодно, ошибка возникает уже при компиляции специализированного генерика, хотя лучше всего если бы была ошибка например “Данный тип не подходит для специализации” или “Данный тип нарушает концепцию генерика”.
Тут появилось слово “концепция”. Это слово появилось в C++ в стандарте 2008, хотя шаблоны уж двадцать лет как есть. Это те же самые ограничения. Например можно создать концепцию “числовой тип” и уже после этого генерик нельзя специализировать строкой. А пока концепции еще не реализованы в C++, то никто не запрещает специализировать генерик строкой. Более того, может оказаться, что компиляция пройдет (потому что генерик использовал только “больше”, “равно” и “плюс”), но после того, как в новой версии разработчик задействовал и “минус”, то компиляция уже не пройдет.
Поэтому имхо, лучше использовать в качестве концепций классы и интерфейсы. Концепция определяется самим классом. Конечно, с простыми типами тогда уже ничего не получится.
3)генерики нарушают модульность. Привычная схема “декларация отдельно, реализация отдельно” уже не работает. В С++ вместо h и cpp появляются файлы без расширения, которые нужно компилировать при каждой сборке (утрирую), привычная схема линковки, понятное дело, уже не работает. Генерик+dll это вообще убийственная связка=) Еще много проблем этого уровня, число решений лучше считать по числу компиляторов C++, которые поддерживают экспорт шаблонов (кажется, он всего один=)).

На мой взгляд, лучше всего – это сделать более совершенные концепции рефакторинга кода, но это весьма далекие перспективы.
Аватара пользователя
Astralis
новенький
 
Сообщения: 45
Зарегистрирован: 06.06.2007 20:33:05
Откуда: Tvercity-Annet

Re: Наследование шаблонов

Сообщение runewalsh » 26.05.2010 18:28:11

В случаях вроде следующего компилятор (2.4.0) спокойно делает многократный копипаст, без которого вполне можно обойтись:
Код: Выделить всё
type
  generic tStack< T > = class
    // ...
  end;

  tType1 = int64;
  tType2 = ansistring;
  tType3 = specialize tStack< pointer >;

  pType1 = ^tType1;
  pType2 = ^tType2;
  pType3 = ^tType3;

  tStack1 = specialize tStack< pType1 >;
  tStack2 = specialize tStack< pType2 >;
  tStack3 = specialize tStack< pType3 >;

Причём независимо от настроек оптимизации. Snapshot'ы из SVN не качал, они сделают из этого одну копию кода tStack< pointer > вместо четырёх?
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 579
Зарегистрирован: 27.04.2010 00:15:25

Re: Наследование шаблонов

Сообщение MageSlayer » 27.05.2010 21:26:38

runewalsh писал(а):Причём независимо от настроек оптимизации. Snapshot'ы из SVN не качал, они сделают из этого одну копию кода tStack< pointer > вместо четырёх?


Неясно, по какой причине компилятор должен эти типы объединять.
Ansistring - это однозначно не только указатель. Как минимум есть еще счетчик ссылок, и соответственно код, для него должен быть другой.
Pointer - случай вообще платформенно-зависимый. Где-то 16бит, где-то 32 и 64.

Так что здесь не оптимизация, а ИИ нужен.
В языках (в основном динамических), где все объекты - указатели/ссылки (то есть по факту имеют общего предка), такая оптимизация возможна. FPC - не этот случай.

Я лично использую макросы явно. Использую для этого m4.
Тут возможностей для оптимизации на этапе компиляции намного больше.
MageSlayer
постоялец
 
Сообщения: 216
Зарегистрирован: 07.09.2006 12:30:44

Re: Наследование шаблонов

Сообщение runewalsh » 27.05.2010 22:51:37

MageSlayer писал(а):Неясно, по какой причине компилятор должен эти типы объединять.

Потому что все 4 раза генерик специализируется, фактически, типом pointer:
Код: Выделить всё
tType3 = specialize tStack< pointer >;
tStack1 = specialize tStack< pType1 >;
tStack2 = specialize tStack< pType2 >;
tStack3 = specialize tStack< pType3 >;
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 579
Зарегистрирован: 27.04.2010 00:15:25

Re: Наследование шаблонов

Сообщение MageSlayer » 27.05.2010 23:26:35

runewalsh писал(а):Потому что все 4 раза генерик специализируется, фактически, типом pointer


А, сорри. По инерции подумал, что это объекты.
Следующий код вроде работает. Если не считать что inline в объявлении глючит.
Этот баг зарепортил - http://bugs.freepascal.org/view.php?id=16582

Код: Выделить всё
program Project1;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
    cthreads,
  {$ENDIF}{$ENDIF}
  Classes, sysutils;

type

  { T1 }

  generic T1<_T> = class
    private
      i:_T;
      procedure SetF(v:_T);
      function GetF:_T;
  end;

  TPointerList = specialize T1<Pointer>;

  { TPointerList2 }

  generic TPointerList2<_T2> = class(TPointerList)
    public
      procedure SetF(v:_T2);//inline; - дает ошибку компиляции
      procedure WriteLn;
  end;

  TPointerListInt = specialize TPointerList2<PInteger>;
  TPointerListDouble = specialize TPointerList2<PDouble>;

  { T1 }

procedure T1.SetF(v: _T);
begin
  i:=v;
end;

function T1.GetF: _T;
begin
  Result:=i;
end;

{ TPointerList2 }

procedure TPointerList2.SetF(v: _T2); inline;
begin
  inherited SetF( Pointer(v) );
end;

procedure TPointerList2.WriteLn;
var S:string;
begin
  S:=Format('%P', [i] );
  System.WriteLn(S);
end;

var IntO:TPointerListInt;
    DoubleO:TPointerListDouble;
begin
  IntO:=TPointerListInt.Create;
  IntO.SetF( PInteger(nil) );
  IntO.WriteLn;
  IntO.Free;

  DoubleO:=TPointerListDouble.Create;
  DoubleO.SetF( PDouble(nil) );
  DoubleO.WriteLn;
  DoubleO.Free;
end.
MageSlayer
постоялец
 
Сообщения: 216
Зарегистрирован: 07.09.2006 12:30:44

Re: Наследование шаблонов

Сообщение runewalsh » 28.05.2010 01:28:56

А если добавить inline не в объявлении, а в реализации?
Код: Выделить всё
procedure T1.SetF(v: _T); inline;
begin
  i:=v;
end;

Вроде работает... по ассемблерному листингу не похоже, что инлайнится, но это мелочи :)
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 579
Зарегистрирован: 27.04.2010 00:15:25

Re: Наследование шаблонов

Сообщение runewalsh » 07.06.2010 20:30:30

Сабж - наследование шаблонов можно заменить композицией:
Код: Выделить всё
  generic tStack< T > = class
  protected
    _asList : specialize tList< T >;
  public
    constructor Create;
    destructor Destroy; virtual;
    procedure Push( const newItem : T );
    function Pop : T;
    property Depth : tUInt read _asList.count;
  end;

Здесь операции со стеком преобразуются в операции со списком.
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 579
Зарегистрирован: 27.04.2010 00:15:25

Re: Наследование шаблонов

Сообщение zub » 21.03.2014 00:15:05

Как наследоваться от генерика в синтаксисе fpc?
В {$mode Delphi} так:
Код: Выделить всё
TMyMap <TKey, TValue, TCompare> = class(TMap<TKey, TValue, TCompare>)

а в {$mode objfpc} чето не соображу и не нагуглю как(( нельзя чтоли?
zub
долгожитель
 
Сообщения: 2887
Зарегистрирован: 14.11.2005 23:51:26

Re: Наследование шаблонов

Сообщение Дож » 21.03.2014 00:40:53

specialize пропустили
Код: Выделить всё
generic TMyMap <TKey, TValue, TCompare> = class(specialize TMap<TKey, TValue, TCompare>)
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Наследование шаблонов

Сообщение zub » 21.03.2014 01:36:35

Спасибо, помогло!
Еще маленький вопрос не по теме:
в {$mode Delphi} можно в поинтер присвоить 0, это вызывает только варнинг
Warning: Converting 0 to NIL

в {$mode objfpc} такой номер уже не прокатывает - будь добр использовать NIL

поэтому шаблон
Код: Выделить всё
TMyMap <TKey, TValue, TCompare> = class(TMap<TKey, TValue, TCompare>)
  ...
  function MyGetValue(key:TKey):TValue;inline;
  ...
end;
...
function TMyMap<TKey, TValue, TCompare>.MyGetValue;
var
   Iterator:TMyMap<TKey, TValue, TCompare>.TIterator;
begin
  Iterator:=Find(key);
  if  Iterator=nil then
                       result:=0
                   else
                       begin
                            result:=Iterator.GetValue;
                            Iterator.Destroy;
                       end;
end;

в режиме совместимости с delphi можно специализировать для TValue=pointer, а в режиме objfpc только для числовых типов.
Какой директивой можно поправить ситуацию разрешив использовать присвоение нуля в указатель?
zub
долгожитель
 
Сообщения: 2887
Зарегистрирован: 14.11.2005 23:51:26

Re: Наследование шаблонов

Сообщение Дож » 21.03.2014 14:23:49

Явное приведение не решает проблему?
Код: Выделить всё
result:=TValue(0);


Кроме того, в 2.7+ есть новая фича Default, можно записать result:=Default(TValue);
Код: Выделить всё
[doj@korica ~/temp]$ cat def.pas
type
  generic A<T> = object function Get: T; end;
  TP = specialize A<Pointer>;
  TI = specialize A<Integer>;

function A.Get: T; begin Get := Default(T); end;

var
  P: TP;
  I: TI;

begin
  Writeln(I.Get, ' ', PtrInt(P.Get));
end.
[doj@korica ~/temp]$ fpc def.pas && ./def
0 0
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Наследование шаблонов

Сообщение zub » 21.03.2014 14:37:00

Приведение типа помогает, но в памяти вертелось чтото подобное {$POINTERMATH ON}.
Память подвела {$POINTERMATH ON} не помогает, а подобного ничего нет

Кроме того, в 2.7+ есть новая фича Default, можно записать result:=Default(TValue);

Спасибо, полезная фича
zub
долгожитель
 
Сообщения: 2887
Зарегистрирован: 14.11.2005 23:51:26

Re: Наследование шаблонов

Сообщение alexs » 21.03.2014 16:04:26

zub
А зачем вам такое извращение? Зачем адреса и числа смешивать? Это же разные сущности. И чем nil не устраивает?
Аватара пользователя
alexs
долгожитель
 
Сообщения: 4064
Зарегистрирован: 15.05.2005 23:17:07
Откуда: г.Ставрополь

Re: Наследование шаблонов

Сообщение zub » 21.03.2014 16:34:40

Возможно извращение, но если по сути - циферки что в поинтере что в числовом типе одинаковые.
Задача сериализовать свои графические данные в "чужой" формат (DXF).
Внутри программы доступ к графическим сущностям через указатели (перекрестные ссылки через нихже), в сериализованном виде все сущности имеют хэндлы и соответственно перекрестные ссылки через эти хэндлы.
Вариант использовать в качестве хэндла напрямую содержимое указателя по некоторым причинам не проходит, вот и приходится при загрузке\выгрузке хранить соответствия поинтер-хэндл и хэндл-поинтер для ускорения поиска нужных графических сущностей.
zub
долгожитель
 
Сообщения: 2887
Зарегистрирован: 14.11.2005 23:51:26

Re: Наследование шаблонов

Сообщение alexs » 21.03.2014 17:06:49

Я про чисто алгоритмический аспект. Понятно - что внутренне - это всё 1 или 0. Но для человека понятнее - если видишь nil в присвоении - понимаешь, что тут явно Pointer или его производные. А если 0 - то это число (перечисляемое значение).
Аватара пользователя
alexs
долгожитель
 
Сообщения: 4064
Зарегистрирован: 15.05.2005 23:17:07
Откуда: г.Ставрополь

Пред.След.

Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 2

Рейтинг@Mail.ru