Статические классы и дженерики (Delphi)
От: Khimik  
Дата: 07.12.18 07:40
Оценка:
Я оптимизировал один алгоритм в своей программе, и всё время возникали баги-проблемы (больше всего от неудаления динамических массивов и неосторожной передачей этих массивов как параметры функций). В итоге я решил основательно всё переписать, в частности заменил большинство своих динамических массивов на вот такие классы:

TIntArray=class(TSafeObject)
private
fitems:pintarr;
fcount,fcapacity:integer;
procedure SetCapacity(newcapacity:integer);
procedure SetCount(newcount: integer);
function GetItem(index: integer): integer;
procedure SetItem(index: integer; Value: integer);

public
property Capacity:integer read fcapacity write SetCapacity;
property Count:integer read fcount write SetCount;
property Items[index:integer]:integer read GetItem write SetItem; default;
procedure Assign(otherarray:tintarray);
procedure RoundAssign(othervalarray:tdoublearray);
function Find(value:integer):integer;
procedure Clear;
procedure Grow;
procedure Add(value:integer);
procedure AddNew(value:integer);//Добавляет элемент, если его еще нет в массиве
procedure AddArray(otherarray:tintarray);
procedure AddNewValuesFromArray(otherarray:tintarray);
procedure Delete(index:integer);
procedure SwapPoints(pointnum1,pointnum2:integer);//меняет местами два элемента
procedure MoveItemToTheFirst(itemnum:integer);//переводит указанный элемент в начало массива
//(остальные элементы сдвигаются на единицу вверх)
procedure ExcludeOtherArray(otherarray:tintarray);//Удаляет все элементы, входящие во второй массив.
procedure KeepOtherArray(otherarray:tintarray);//Оставляет только элементы, входящие во второй массив.
procedure SetBackPointers(otherarray:tintarray);//Во втором массиве должны находиться указатели
//на текущий (не повторяющиеся); в items расставляются номера этих указателей или -1, где их нет.
//Перед вызовом должен быть установлен count
function GetUniqueValsNumbers:tintarray;//Возвращает массив, состоящий из 0, 1, 2..., соответствующим
//уникальным числам в исходном массиве
procedure Sort(ascending:boolean);
procedure FillWithValues(valuesbeg,valueslast:integer);
procedure IncValues(add:integer);
procedure AddToStream(stream:tstream);
procedure ReadFromStream(stream:tstream);
procedure WriteToStream(stream:tstream);
destructor Destroy; override;
end;


Эти классы всем хороши (особенно после того как я подключил к программе свой сборщик мусора
Автор: Khimik
Дата: 30.11.18
), за исключением скорости: они представляют собой указатель на указатель и ещё на индекс, т.е. три указателя.
Поэтому я думаю заменить часть этих классов на такие record-ы:

TRiIntArray=record
FItems:pintarr;
FCount,FCapacity:integer;
FTag:integer;
procedure SetCapacity(newcapacity:integer);
procedure SetCount(newcount: integer);
function GetItem(index: integer): integer;
procedure SetItem(index: integer; Value: integer);
property Capacity:integer read fcapacity write SetCapacity;
property Count:integer read fcount write SetCount;
property Items[index:integer]:integer read GetItem write SetItem; default;
procedure Assign(otherarray:triintarray);
function Find(value:integer):integer;
procedure Clear;
procedure Grow;
procedure Add(value:integer);
procedure Delete(index:integer);
procedure Sort(ascending:boolean);
procedure AddToStream(stream:tstream);
procedure ReadFromStream(stream:tstream);
procedure WriteToStream(stream:tstream);
procedure Initialize;
procedure Finalize;
end;


Можно ли назвать такие record-ы статическими классами? У них есть инкапсуляция, но нет наследования и полиморфизма (наследование кстати авторам ЯП было бы нетрудно добавить).
Проблема для меня в том, что нельзя писать такой record для каждого нового типа данных, а ещё по этой причине не получится так сделать двумерные динамические массивы. Но может быть эту проблему можно решить с помощью дженериков? Я пока не освоился с дженериками в Delphi, примеры на сайте связаны с классами, а с рекордами их не применишь?

Ещё мне интересно, как выглядят статические классы в C++, насколько похожие там были бы проблемы.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Re: Статические классы и дженерики (Delphi)
От: swame  
Дата: 08.12.18 06:46
Оценка:
Здравствуйте, Khimik, Вы писали:

K>Можно ли назвать такие record-ы статическими классами? У них есть инкапсуляция, но нет наследования и полиморфизма (наследование кстати авторам ЯП было бы нетрудно добавить).


Нельзя. Для рекордов и классов по разному работает управление памятью и передача параметров.

K>Проблема для меня в том, что нельзя писать такой record для каждого нового типа данных, а ещё по этой причине не получится так сделать двумерные динамические массивы. Но может быть эту проблему можно решить с помощью дженериков?


Можно.

>>Я пока не освоился с дженериками в Delphi, примеры на сайте связаны с классами, а с рекордами их не применишь?


Для таких задач я бы еще посмотрел на возможность в современных Delphi использовать динамический массив + хелперы к нему.
Но иметь в виду что в динамическом массиве медленно работает изменением размера.
Поэтому У нас аналогичная штука организована так

  RArray<T> = record
  private
    a: TArray<T>;
    const START_CAP = 16;
    procedure grow(); inline;
    function getItem(aIndex: Integer): T;
    procedure setItem(aIndex: Integer; aValue: T);
    function getLast(): T;
    function getFirst(): T;

  public
    count: Integer;

    constructor Create(aStartCap: Integer); overload;
    constructor Create(aProc: TProcProc<T>); overload;
    constructor Create(aList: TList<T>); overload;
    constructor Create(aArray: RArray<T>); overload;
    constructor Create(aT: T); overload;

    class function init(): RArray<T>; static;
    class function empty(): RArray<T>; static;
    class function cast<Ch: T>(aArray: RArray<Ch>): RArray<T>; static;

    procedure add(aElem: T); overload;
    procedure add<V: T>(aArray: RArray<V>); overload;

    procedure add(aArray: RArray<T>); overload;
    procedure addTo(var aArray: RArray<T>);
    procedure clear();
    procedure delete(aIndex: Integer);
    procedure sort(aComparer: IComparer<T>);
    procedure asc(aFunc: TFunc<T, Double>);
    procedure desc(aFunc: TFunc<T, Double>);
    procedure reverse();
    procedure assign(aSource: RArray<T>; aOperator: TListAssignOp = laCopy);
    procedure assignOr(aSource: RArray<T>; aToken: TToken<T>);
    function indexOf(aItem: T): Integer; overload;
    function GetEnumerator: RArrayEnumerator<T>;

    function some(aBool: TTBool<T>): Boolean;
    function every(aBool: TTBool<T>): Boolean;

    function forEach(aProc: TProc<T>): RArray<T>;
    function map<TOut>(aFunc: TFunc<T, TOut>): RArray<TOut>; overload;
    function map<TOut>(aProc: TOne2ManyP<T, TOut>): RArray<TOut>; overload;
    function map(aFunc: TFunc<T, T>): RArray<T>; overload;
    function filter(aFunc: TTBool<T>): RArray<T>;
    function distinct(): RArray<T>;
    function first(): RArray<T>;
    function toList(): TList<T>;
    function stream(): TRStream<T>;

    property last: T read getLast;
    property firstItem: T read getFirst;
    property Item[index: integer]: T read getItem write setItem; default;
  end;

  TRStream<T> = class
  private
    fFeProc: TProc<T>;
    fIsCanceled: Boolean;
    fOnClose: TProc;
    class var counter: Integer;

    function _or(aBool: TTBool<T>): Boolean; overload;
    function _every(aBool: TTBool<T>): Boolean; overload;

  protected
    procedure beforeClose(); virtual;

  public
    class constructor Create();
    constructor Create();
    destructor Destroy; override;
    procedure close(); virtual;

    function forEachS(aProc: TProc<T>): TRStream<T>; virtual;
    procedure foreach(aProc: TProc<T>); virtual;
    procedure iterate();
    procedure cancel(); virtual;

    function some(aBool: TTBool<T>): Boolean;
    function every(aBool: TTBool<T>): Boolean;

    function _or(aBool: TTBool<T>; var aResult: Boolean): TRStream<T>; overload;
    function thread(): TRStream<T>;
    function map<R>(aFunc: TFunc<T, R>): TRStream<R>; overload;
    function map<R>(aProc: TOne2ManyP<T, R>): TRStream<R>; overload;
    function map<R>(aFunc: TArrayFunc<T, R>): TRStream<R>; overload;
    function map<R; Context: class>(aProc: TProcProcVar2<T, R, Context>): TRStream<R>; overload;
    function map<R>(aFunc: TListFunc<T, R>): TRStream<R>; overload;
    function map<R>(aFunc: TOne2StreamF<T, R>): TRStream<R>; overload;
    function map<R>(aFunc: TStream2StreamF<T, R>): TRStream<R>; overload;
    function map(aFunc: TFunc<T, T>): TRStream<T>; overload;
    function map(aProc: TOne2ManyP<T, T>): TRStream<T>; overload;
    function map(aProcs: array of TOne2ManyP<T, T>): TRStream<T>; overload;
    function map(aFunc: TOne2StreamF<T, T>): TRStream<T>; overload;
    function map(aFunc: TStream2StreamF<T, T>): TRStream<T>; overload;
    function filter(aFunc: TTBool<T>): TRStream<T>;
    function first(): TRStream<T>;
    function distinct(aPool: TTokenPool<T> = nil): TRStream<T>; overload;
    function distinct(aToken: TToken<T>): TRStream<T>; overload;
    function tolist(aList: TList<T>): TRStream<T>;
    procedure list(aList: TList<T>);
    function toArray(var aArray: RArray<T>): TRStream<T>; overload;
    function toArray(): RArray<T>; overload;
    function debug(var aList: TList<T>): TRStream<T>;
    function reduce(aFunc: TReduceFunc<T>): TRStream<T>;
    function notMarked(aToken: TToken<T>): TRStream<T>;
    function unmark(aToken: TToken<T>): TRStream<T>;
    function mark(aToken: TToken<T>): TRStream<T>;

    function onClose(aProc: TProc): TRStream<T>;

    class function just(aT: T): TRStream<T>;
    class function stream(aList: TList<T>): TRStream<T>; overload;
    class function stream(aArray: RArray<T>): TRStream<T>; overload;
    class function stream(aProc: TProcProc<RArray<T>>): TRStream<T>; overload;
  end;
Отредактировано 08.12.2018 7:04 swame . Предыдущая версия . Еще …
Отредактировано 08.12.2018 6:48 swame . Предыдущая версия .
Re[2]: Статические классы и дженерики (Delphi)
От: Khimik  
Дата: 08.12.18 10:25
Оценка:
S>Для таких задач я бы еще посмотрел на возможность в современных Delphi использовать динамический массив + хелперы к нему.
S>Но иметь в виду что в динамическом массиве медленно работает изменением размера.
S>Поэтому У нас аналогичная штука организована так

Спасибо, я начал осваиваться с этими объектами.
Но у вас сам массив, где хранятся собственно данные, представлен в виде стандартного динамического массива в Delphi. Я хочу использовать более древние варианты — указатели и всякие allocmem/reallocmem. Но боюсь, что дженерики с такими типами данных уже не получится использовать. Я набрал так:

TArr<MyType> = array[0..999999999] of MyType;
PArr<MyType>=^tarr<MyType>;


Компилятор говорит "Type parameters not allowed in this type". Тут ничего нельзя придумать?

Если использовать древние массивы вместо стандартных, возникают такие плюсы:
1) Можно выделять новую память немного быстрее за счёт того, что она не будет заполняться нулями;
2) Можно вообще не выделять память через allocmem, а просто поменять указатель, подключив память из другого объекта. Тогда возникает много возможностей типа собственного менеджера памяти.
3) С обычными динамическими массивами у меня было много багов из-за того, что я передавал их как аргументы функций, или функции их возвращали, и возникала разная путаница, т.к. я не очень представляю что такое стандартный динамический массив с точки зрения компилятора (например, когда он передаётся как аргумент, что конкретно хранится в стеке). С древними массивами я буду контролировать такие моменты.

И мне интересно, как в C++ решаются аналогичные задачи.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Отредактировано 08.12.2018 11:16 Khimik . Предыдущая версия .
Re[2]: Статические классы и дженерики (Delphi)
От: Khimik  
Дата: 08.12.18 11:19
Оценка:
K>>Можно ли назвать такие record-ы статическими классами? У них есть инкапсуляция, но нет наследования и полиморфизма (наследование кстати авторам ЯП было бы нетрудно добавить).

S>Нельзя. Для рекордов и классов по разному работает управление памятью и передача параметров.


Так я и говорю, что статический класс отличается от обычного (динамического) тем, что обычный класс — это просто ссылка, а статический — это большая переменная. Если, например, в процедуре объявляется обычный класс — в стеке хранится только указатель, а если статический — в стеке хранятся все его поля, поэтому в пределах процедуры работа с этими полями будет быстрее.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Re[3]: Статические классы и дженерики (Delphi)
От: swame  
Дата: 08.12.18 13:32
Оценка:
Здравствуйте, Khimik, Вы писали:

S>>Для таких задач я бы еще посмотрел на возможность в современных Delphi использовать динамический массив + хелперы к нему.

S>>Но иметь в виду что в динамическом массиве медленно работает изменением размера.
S>>Поэтому У нас аналогичная штука организована так

K>Спасибо, я начал осваиваться с этими объектами.

K>Но у вас сам массив, где хранятся собственно данные, представлен в виде стандартного динамического массива в Delphi. Я хочу использовать более древние варианты — указатели и всякие allocmem/reallocmem. Но боюсь, что дженерики с такими типами данных уже не получится использовать. Я набрал так:

K>
K>TArr<MyType> = array[0..999999999] of MyType;
K>PArr<MyType>=^tarr<MyType>;
K>


K>Компилятор говорит "Type parameters not allowed in this type". Тут ничего нельзя придумать?


Можно, но не нужно на уровне указателей работать с дженериками.

K>Если использовать древние массивы вместо стандартных, возникают такие плюсы:


Это все минусы

K>1) Можно выделять новую память немного быстрее за счёт того, что она не будет заполняться нулями;


Заполнить нулями в 90% случаев все равно нужно, и это заполнение на порядок быстрее выделения памяти.
Я не знаю точно как сейчас, но при включенной оптимизации оно может и не заполняется.

K>2) Можно вообще не выделять память через allocmem, а просто поменять указатель, подключив память из другого объекта. Тогда возникает много возможностей типа собственного менеджера памяти.


Это то самое "прострелить себе ногу". Не надо подменять указатели. Их можно передавать, тогда понятно что делается.

K>3) С обычными динамическими массивами у меня было много багов из-за того, что я передавал их как аргументы функций, или функции их возвращали, и возникала разная путаница, т.к. я не очень представляю что такое стандартный динамический массив с точки зрения компилятора (например, когда он передаётся как аргумент, что конкретно хранится в стеке).


Что мешает разобраться как работают массивы?
K>С древними массивами я буду контролировать такие моменты.

Нет то что ты делаешь быстро превратится в неконтролируемую лапшу.


K>И мне интересно, как в C++ решаются аналогичные задачи.
Re[3]: Статические классы и дженерики (Delphi)
От: swame  
Дата: 08.12.18 13:40
Оценка:
Здравствуйте, Khimik, Вы писали:

K>>>Можно ли назвать такие record-ы статическими классами? У них есть инкапсуляция, но нет наследования и полиморфизма (наследование кстати авторам ЯП было бы нетрудно добавить).


S>>Нельзя. Для рекордов и классов по разному работает управление памятью и передача параметров.


K>Так я и говорю, что статический класс отличается от обычного (динамического) тем, что обычный класс — это просто ссылка, а статический — это большая переменная.

Нет, большая переменyая это рекорд. Статическими объектами иногда называют "объекты" из времен турбо паскаля, по возможностям похожие на современные рекорды (без наследования), но по другому инициализирующиеся и передающиеся. Их и сейчас можно объявить

TMyObject = object
A: integer;
end;

K>Если, например, в процедуре объявляется обычный класс — в стеке хранится только указатель, а если статический — в стеке хранятся все его поля, поэтому в пределах процедуры работа с этими полями будет быстрее.


Работа будет быстрее, но подготовка передачи через параметры будет медленнее, поэтому для скорости всегда передают по указателю
Re[4]: Статические классы и дженерики (Delphi)
От: Khimik  
Дата: 08.12.18 15:34
Оценка:
K>>1) Можно выделять новую память немного быстрее за счёт того, что она не будет заполняться нулями;

S>Заполнить нулями в 90% случаев все равно нужно, и это заполнение на порядок быстрее выделения памяти.

S>Я не знаю точно как сейчас, но при включенной оптимизации оно может и не заполняется.

Хаха, значит при включенной оптимизации и классы при инициации могут не заполняться нулевыми значениями? Не знаю как у других, лично моя программа в этом режиме конкретно полетит.
Т.е. я согласен, что в 90% случаев надо всё заполнять нулями, но есть ещё какая-то то пара процентов, когда не нужно и при этом важна скорость.

Насчёт скорости выделения памяти – это отдельная тема, см. ниже.


K>>2) Можно вообще не выделять память через allocmem, а просто поменять указатель, подключив память из другого объекта. Тогда возникает много возможностей типа собственного менеджера памяти.


S>Это то самое "прострелить себе ногу". Не надо подменять указатели. Их можно передавать, тогда понятно что делается.


С классами всегда всё достаточно понятно, на то они и классы.


K>>3) С обычными динамическими массивами у меня было много багов из-за того, что я передавал их как аргументы функций, или функции их возвращали, и возникала разная путаница, т.к. я не очень представляю что такое стандартный динамический массив с точки зрения компилятора (например, когда он передаётся как аргумент, что конкретно хранится в стеке).


S>Что мешает разобраться как работают массивы?



Вот смотрите. Рассмотрим четыре варианта передачи массива как аргумент функции:

type
TArray=array[0..100] of integer;

function CalculateSumInArray(arr:tarray):integer;
function CalculateSumInArray(const arr:tarray):integer;
function CalculateSumInArray(arr:array of integer):integer;
function CalculateSumInArray(const arr:array of integer):integer;

Какие данные будут передаваться чезез стек в этих четырёх вариантах? В первом, очевидно, в стеке будет храниться весь массив, а во втором – указатель на этот массив. А в третьем и четвёртом случае – соответственно указатель и указатель на указатель?


S>Работа будет быстрее, но подготовка передачи через параметры будет медленнее, поэтому для скорости всегда передают по указателю


Я планирую потестировать всё это как следует.

Вот ещё одно преимущество:

4) Если подменять указатели в объекте-массиве вместо выделения памяти, то можно написать собственный менеджер памяти. По-моему это несложная задача: должен быть объект, который изначально выделяет себе, например, 10 МБ памяти, и дальше он будет раздавать эту память моим динамическим массивам. Кроме скорости, преимущество такого менеджера – он позволит сделать собственный контроль за утечками и мусором.
"Ты должен сделать добро из зла, потому что его больше не из чего сделать". АБ Стругацкие.
Re[5]: Статические классы и дженерики (Delphi)
От: swame  
Дата: 10.12.18 18:41
Оценка:
Здравствуйте, Khimik, Вы писали:

K>>>1) Можно выделять новую память немного быстрее за счёт того, что она не будет заполняться нулями;


S>>Заполнить нулями в 90% случаев все равно нужно, и это заполнение на порядок быстрее выделения памяти.

S>>Я не знаю точно как сейчас, но при включенной оптимизации оно может и не заполняется.

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

K>Т.е. я согласен, что в 90% случаев надо всё заполнять нулями, но есть ещё какая-то то пара процентов, когда не нужно и при этом важна скорость.

Я обычно очистку руками на всякий случай, заполнение нулями никогда не занимало заметное время.


K>>>3) С обычными динамическими массивами у меня было много багов из-за того, что я передавал их как аргументы функций, или функции их возвращали, и возникала разная путаница, т.к. я не очень представляю что такое стандартный динамический массив с точки зрения компилятора (например, когда он передаётся как аргумент, что конкретно хранится в стеке).


S>>Что мешает разобраться как работают массивы?



K>Вот смотрите. Рассмотрим четыре варианта передачи массива как аргумент функции:


K>
K>type
K>TArray=array[0..100] of integer;

K>function CalculateSumInArray(arr:tarray):integer;
K>function CalculateSumInArray(const arr:tarray):integer;
K>function CalculateSumInArray(arr:array of integer):integer;
K>function CalculateSumInArray(const arr:array of integer):integer;
K>

K>Какие данные будут передаваться чезез стек в этих четырёх вариантах? В первом, очевидно, в стеке будет храниться весь массив, а во втором – указатель на этот массив. А в третьем и четвёртом случае – соответственно указатель и указатель на указатель?

Вот тут расписано достаточно подробно
https://www.transl-gunsmoker.ru/2009/09/blog-post.html

S>>Работа будет быстрее, но подготовка передачи через параметры будет медленнее, поэтому для скорости всегда передают по указателю


K>Я планирую потестировать всё это как следует.


Это все реально мало влияет на производительность, если не делать совсем уж глупостей.

K>Вот ещё одно преимущество:


K>4) Если подменять указатели в объекте-массиве вместо выделения памяти, то можно написать собственный менеджер памяти. По-моему это несложная задача: должен быть объект, который изначально выделяет себе, например, 10 МБ памяти, и дальше он будет раздавать эту память моим динамическим массивам. Кроме скорости, преимущество такого менеджера – он позволит сделать собственный контроль за утечками и мусором.


Разберись c FastMM, там есть все что нужно.

https://www.gunsmoker.ru/2009/05/blog-post_24.html
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.