(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Smart pointers в Object Pascal Delphi 2009. В поисках решения

Источник: delphiplus
Алексей Коленько

Автор: Алексей Коленько

Вступление.

Данная статья знакомит с так называемой техникой Smart pointers. Собственно это никакие не указатели. Для них не существует специальной синтаксической конструкции и ключевых слов. Правильнее под этим термином понимать технику или набор приемов, рецептов создания типов данных с автоматически  распределяемой и освобождаемой памятью в Object Pascal Delphi 2009. Я бы даже определил как достаточно мощный шаблон проектирования классов в Delphi 2009 for Win32. В сочетании с другими средствами, введенными в Delphi 2009, эта техника может дать программисту на Object Pascal в руки мощные средства для написания сложных верифицируемых программ или прикладных библиотек с минимальными трудозатратами, легко воспринимаемым кодом и количеством ошибок, пониженным по сравнению с более традиционными подходами.

Для чего эту технику можно использовать?

Давайте, вспомним, как мы все работаем с объектами.  Как правило, это выглядит подобно коды на следующей врезке.

type

  TSomeClass = class

  private

//  ................

  protected

//  ................

  public

    constructor Create; 

    destructor Destroy; override; 

//  ................

  published

//  ................

  end;

// .......................

// .......................

implementation


procedure SomeProcedure({..Что-то передаем....});

var

  SomeClass: TSomeClass;

begin

// Создаем экземпляр класса.

  SomeClass:=TSomeClass.Create;

  try

// .......................

// .......................

// .....Что-то делаем.....

// .......................

//  ......................

  finally

// Удаляем экземпляр класса.

    SomeClass.Free;

  end;

// .......................

// .......................

// .....Что-то еще делаем.....

// .......................

// .......................

end;

// .......................

// .......................

end.


Первое что мы делаем: создаем экземпляр класса и присваиваем его соответствующей переменной, затем выполняем некоторую полезную работу с этим экземпляром. В заключение, после того как объект перестал быть нужным мы должны освободить выделенную под него память. Каждый раз мы должны проходить путь от

SomeClass:=TSomeClass.Create;

до

SomeClass.Free;

Собственно это не сложно и не должно вызывать осложнений у более или менее опытного программиста. Однако, нередко возникает дилемма: стоит ли или не стоит вызывать в данном случае конструктор или освобождать память ненужного к этому времени экземпляра класса.

Можно поступить по-другому: объявить интерфейс соответствующего класса, присвоить созданный экземпляр класса соответствующей интерфейсной переменной, а дальше работать с ней.

type

  ISomeClass = interface(IInterface)

//  ................

//  ................

//  ................

  end;

  TSomeClass = class( TIntefacedObject, ISomeClass)

  private

//  ................

  protected

//  ................

  public

    constructor Create; 

    destructor Destroy; override; 

//  ................

  published

//  ................

  end;

implementation

// .......................

// .......................

   procedure SomeProcedure({......});

   var

     SomeClass: ISomeClass;

   begin

// Создаем экземпляр класса.

     SomeClass:=TSomeClass.Create;

     try

// .......................

// .......................

// .....Что-то делаем.....

// .......................

// .......................

     except

// .......................

// .......................

// Обрабатываем исключение.

// .......................

// .......................

     end;

end;

// .......................

// .......................

end.

При выходе из процедуры менеджер памяти удаляет ссылку на интерфейс. Механизм, заложенный  в класс TIntefacedObject и интерфейс IInterface, подсчитывает количество ссылок на указанный экземпляр класса TSomeClass и если число ссылок становиться равным нулю - удаляет данный экземпляр класса из памяти.

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

// .......................

// .......................

// Создаем экземпляр класса.

     SomeClass:=TSomeClass.Create;

// .......................

// .......................

// .....Что-то делаем.....

// .......................

Хотелось бы избавиться от такой нужной для экземпляра класса, но достаточно рутинной для программиста процедуры. Сделать процедуру выделения и утилизации памяти экземпляра класса более прозрачной для прикладного программиста. С другой стороны, это дело вкуса… Вся эта "кухня" нужна при программировании под Win32. При работе с версией Delphi for .Net практически все это становится ненужным.

Как использовать технику "интеллектуальных указателей"

Дальше в попытке пояснить технику "интеллектуальных указателей" автор будет придерживаться идей изложенных Марко Кэнту (Marco Cantu) в справочнике по особеностям Delphi 2009 [1].

Рассмотрим задачу с реализацией части библиотеки вычислительной геометрии, связанной с таким интересным геометрическим объектом как "мультиточка". По своей сути "мультиточка" представляет собой некоторый произвольный набор или множество точек, в общем случае многомерного пространства (например, вершины многоугольника на плоскости или множество фактов из некоторого класса или кластера в многомерном признаковом пространстве при решении задач классификации и анализа данных). Для нашей задачи остановимся на 2-мерных точках. 

Определим основной элементарный класс из которого состоитит мультиточка - TPointD2.

    TPointD2 = packed record

    strict private

        FX,

        FY: single;

    private

      function GetX: single;

      procedure SetX( const Value: single);

      function GetY: single;

      procedure SetY( const Value: single);

    public

      procedure Empty;

      function IsEmpty: boolean;

      function Min( const PT: TPointD2): TPointD2;

      function Max( const PT: TPointD2): TPointD2;

      function Distance: single;

      function Angle: single;

      function Invert: TPointD2;

        property X: single read GetX write SetX;

        property Y: single read GetY write SetY;

    end;

Кроме координат на плоскости данный класс (X,Y) содержит некоторое количество методов, которые мы используем в последующих тестах.

  1. Некоторые методы класса TPointD2

procedure Empty;

Инициализирует точку специальными значениями. Создает так называемую  "пустую" точку.

function IsEmpty: boolean;

Метод проверяет является ли точка "пустой".

function Min( const PT: TPointD2): TPointD2;

Вычисляет "минимальную" точку из текущей точки и точки PT. Этот метод один из многочисленных других методов класса TPointD2 предназначенных для реализации специфической для данного класса функциональности.

function Max( const PT: TPointD2): TPointD2;

Вычисляет "максимальную" точку из текущей точки и точки PT. Этот метод один из многочисленных других методов класса TPointD2 предназначенных для реализации специфической для данного класса функциональности.

function Distance: single;

Метод вычисляет квадрат расстояния от начала координат до текущей точки.

function Angle: single;

Метод вычисляет угол между вектором направленным из начала координат в текущую точку.

Function Invert: TPointD2;

Метод возвращает точку, имеющую инвертированные координаты текущей точки:  . Фактически данный метод производит поворот вектора, направленного из начала координат в текущую точку на угол 180° (обращение).

Определим массив (вектор) 2-мерных точек. Благодаря тому что, что в Delphi 2009 появились обобщенные классы (generic) мы можем сделать это очень просто, добавив в секцию uses объявление модулей Generics.Default и Generics.Collections, а в определение типов

    TPointsD2 = class(TList<TPointD2>)

    public

      destructor Destroy; override;

// На время забудем следующее объявление метода GetComparer класса, хотя оно

// нам понадобиться в последующем.     

//      class function GetComparer: TComparerPoint; virtual;

    end;

Благодаря наличию механизма обобщенных классов нам не понадобиться реализовывать или наследовать всю функциональность класса TList. Все что нужно будет сделано компилятором… Немножко нужно сказать о деструкторе.  Собственно, его и не нужно было определять… В данном случае он нужен лишь для демонстрации того как и когда удаляется из памяти экземпляр класса TPointsD2.  

    destructor TPointsD2.Destroy;

    begin

      ShowMessage('Удаляется экземпляр класса TPointsD2!');

      inherited Destroy;

    end;

В реальной задаче можно обойтись и деструктором родительского класса.

Следующим нашим шагом станет собственно первый шаг в определении "интеллектуального указателя" для типа данных "мультиточка" - TMultiPointD2:

Что это нам дает? Пока абсолютно ничего!

    TMultiPointD2 = record

    strict private

        FValue: TPointsD2;

      function GetValue: TPointsD2;

    public

      constructor Create( AValue: TPointsD2); overload;

        property Value: TPointsD2 read GetValue;

    end;

Обратите внимание на то, что мы не использовали нигде возможностями предоставляемыми механизмом обобщенных классов (generics). Собственно, этого и не нужно для данного примера.

Реализация объявленных методов может быть следующей:

      constructor TMultiPointD2.Create( AValue: TPointsD2);

      begin        

        FValue:=AValue;

      end;

      function TMultiPointD2.GetValue: TPointsD2;

      begin

        Result:=FValue;

      end;

На следующей врезке показан вариант использования данного класса.

    Var

      VPT: TPointsD2;

      MPT: TMultiPointD2;

      PT: TPointD2;

    begin

//.........................

//.........................

//.........................

      VPT:=TPointsD2.Create;

      MPT.Create( VPT);

      with PT do

        begin

          X:=0.1;

          Y:=221.3;

        end;

      VPT.Add(PT);

      MPT.Value.Add(PT);

//.........................

//.........................

//.........................

    end;

Видно, что это не дает ничего нам полезного. Более того, по завершении процедуры в памяти мы имеем мусор в виде неосвобожденного экземпляра класса TPointsD2.  Мы в самом чистом виде имеем дело с утечкой памяти. Попытка вызвать его деструктор приведет к краху программы, поскольку в переменной (в закрытом поле FValue) записи MPT мы имеем ссылку на этот объект, которую сделать предварительно равной nil мы не в состоянии.

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

Итак, нам необходимо добавить в определение TMultiPointD2 поле типа IInterface. Реализация этого интерфейса должна поддерживаться  некоторым классом.

Сначала рассмотрим определение этого класса отдельно (см. следующую врезку).

      type

        TFreeTheValue = class(TInterfacedObject)

        private

            FObjectToFree: TObject;

        public

          constructor Create( AObjectToFree: TObject);

          destructor Destroy; override;

        end;

При таком определении реализация методов класса TFreeTheValue очень простая (см. следующую врезку). Поле FObjectToFree будет содержать ссылку на высвобождаемый объект. Его инициализация происходит в конструкторе:

      constructor TFreeTheValue.Create( AObjectToFree: TObject);

      begin

        FObjectToFree:=AObjectToFree;

      End;

Деструктор выполняет всю требуемую от этого класса работу:

      destructor TFreeTheValue.Destroy;

      begin

        FObjectToFree.Free// Все что нам требуется от данного класса!

        inherited Destroy;  

      end;

Определение прикладного класса TMultiPointD2 примет вид показанный на следующей врезке.

    TMultiPointD2 = record

    strict private

        FValue: TPointsD2;

        FFreeTheValue: IInterface;

      function GetValue: TPointsD2;

    private

      type

        TFreeTheValue = class (TInterfacedObject)

        private

            FObjectToFree: TObject;

        public

          constructor Create( AObjectToFree: TObject);

          destructor Destroy; override;

        end;

    public

      constructor Create( AValue: TPoints); overload;

        property Value: T read GetValue;

    end;

//.........................

//.........................

//.........................

Как мы уже говорили, в определение класса внесли поле FFreeTheValue типа IInterface и встроенный тип TFreeTheValue.

Каким образом происходит инициализация FFreeTheValue? В соответствии с предложенной идеологией этим должен заниматься конструктор класса TMultiPointD2. Разработчики Delphi 2009 предлагают следующий вариант, показанный на следующей врезке.

      constructor TMultiPointD2.Create( AValue: TPointsD2);

      begin

        FValue:=AValue;

        FFreeTheValue:=TFreeTheValue.Create( FValue);

      end;

Теперь рассмотрим полностью весь полученный код.

unit TestMultiPoints;

interface

  uses

    Windows, Messages, SysUtils, Variants, Classes, Controls,

    Dialogs,

    Generics.Collections, Generics.Defaults, ExtCtrls;

  type

    TPointD2 = packed record

    strict private

        FX,

        FY: single;

    private

      function GetX: single;

      procedure SetX( const Value: single);

      function GetY: single;

      procedure SetY( const Value: single);

    public

      procedure Empty;

      function IsEmpty: boolean;

      function Min( const PT: TPointD2): TPointD2;

      function Max( const PT: TPointD2): TPointD2;

      function Distance: single;

      function Angle: single;

      function Invert: TPointD2;

        property X: single read GetX write SetX;

        property Y: single read GetY write SetY;

    end;

    TPointsD2 = class(TList<TPointD2>)

    public

      destructor Destroy; override;

//      class function GetComparer: TComparerPoint; virtual;

    end;

    TMultiPointD2 = record

    strict private

        FValue: TPointsD2;

        FFreeTheValue: IInterface;

      function GetValue: TPointsD2;

    private

      type

        TFreeTheValue = class (TInterfacedObject)

        private

            FObjectToFree: TObject;

        public

          constructor Create( AObjectToFree: TObject);

          destructor Destroy; override;

        end;

    public

      constructor Create( AValue: TPoints); overload;

        property Value: TPointsD2 read GetValue;

    end;

implementation

  uses

    Math;

//===========================================================================

// TPointD2.

      function TPointD2.GetX: single;

      begin

        Result:=FX;

      end;

      procedure TPointD2.SetX( const Value: single);

      begin

        FX:=Value;

      end;

      function TPointD2.GetY: single;

      begin

        Result:=FY;

      end;

      procedure TPointD2.SetY( const Value: single);

      begin

        FY:=Value;

      end;

      procedure TPointD2.Empty;

      begin

        Self:=Default(TPointD2); // !!!! Внимание код должен быть изменен!

      end;

      function TPointD2.IsEmpty: boolean;

      begin

        Result:=false; // !!!! Внимание код должен быть изменен!

      end;

      function TPointD2.Min( const PT: TPointD2): TPointD2;

      begin

        Result.Empty;

        if(Self.IsEmpty and (not PT.IsEmpty)) then

          exit(PT);

        if((not IsEmpty) and PT.IsEmpty) then

          exit(Self);

        if(not (Self.IsEmpty or PT.IsEmpty)) then

          begin

            Result.X:=Math.Min( X, PT.X);

            Result.Y:=Math.Min( Y, PT.Y);

        end;

      end;

      function TPointD2.Max( const PT: TPointD2): TPointD2;

      begin

        Result.Empty;

        if(not (Self.IsEmpty or PT.IsEmpty)) then

          begin

            Result.X:=Math.Max( X, PT.X);

            Result.Y:=Math.Max( Y, PT.Y);

        end;

      end;

      function TPointD2.Distance: single;

      begin

        Result:=Sqr(X)+Sqr(Y);

      end;

      function ArcTg( X, Y: double): double;

      begin

        if(X = 0.0) then

          begin

            Result:=Pi/2.0;

            if(Y < 0.0) then

              Result:=-Result;

          end

        else

          Result:=ArcTan2( Y, X);

      end;

      function TPointD2.Angle: single;

      begin

        Result:=ArcTg( X, Y);

      end;

      function TPointD2.Invert: TPointD2;

      begin

        Result.X:=-X;

        Result.Y:=-Y;

      end;

//===========================================================================

// TPointsD2.

      destructor TPointsD2.Destroy;

      begin

        ShowMessage('Удаляется экземпляр класса TPointsD2!');

        inherited Destroy;

      end;

//===========================================================================

// TMultiPointD2.

      constructor TMultiPointD2.Create( AValue: TPoints);

      begin

        FValue:=AValue;

        FFreeTheValue:=TFreeTheValue.Create( FValue);

      end;

      function TMultiPointD2.GetValue: TPointsD2;

      begin

        Result:=FValue;

      end;

//===========================================================================

// TMultiPointD2.TFreeTheValue.

      constructor TMultiPointD2.TFreeTheValue.Create( AObjectToFree: TObject);

      begin

        FObjectToFree:=AObjectToFree;

      end;

      destructor TMultiPointD2.TFreeTheValue.Destroy;

      begin

        FObjectToFree.Free;

        inherited Destroy;

      end;

end.

Таким образом, мы добились требуемой нами функциональности. Как происходит работа можно посмотреть на следующем тестовом примере.

    procedure TForm1.tbTestClick( Sender: TObject);

    var

      VPT: TPointsD2;

      MPT: TMultiPointD2;

      PT: TPointD2;

    begin

      VPT:=TPointsD2.Create;

      MPT.Create( VPT);

      with PT do

        begin

          X:=0.1;

          Y:=221.3;

        end;

      VPT.Add(PT);

      MPT.Value.Add(PT);

    end;

По своей сути этот тот же пример, к которому мы обращались выше.  С учетом внесенных изменений в класс TMultiPointD2 мы добились устранения утечки памяти при вызове тестовой процедуры. О том что объект VPT успешно удаляется свидетельствует появление диалогового окна изображенного на следующем рисунке, и являющегося следствием вызова процедуры ShowMessage из деструктора объекта VPT (TPointsD2).

Сообщение класса при его деструкции

Предела совершенству нет. Да и не во всем, тем более в нашем коде.  Обратим внимание на следующие две строки кода:

      VPT:=TPointsD2.Create;

      MPT.Create( VPT);

Опять мы вызываем конструкторы. Можно ли обойтись без них? Оказывается, можно! Тем более что массив VPT нужен только для методов нашего "интеллектуального указателя". Посмотрим, что можно для этого сделать.

Модифицируем код метода GetValue  следующим образом:

      function TMultiPointD2.GetValue: TPointsD2;

      begin

        if( not Assigned(FFreeTheValue)) then

          Empty;

        Result:=FValue;

      end;

У нас появилась следующие операторы

//.........................

        if( not Assigned(FFreeTheValue)) then

          Empty;

//.........................

Перед тем как возвратить инкапсулированный экземпляр прикладного класса мы смотрим инициирована ли переменная FFreeTheValue или нет. Если переменная FFreeTheValue не инициирована, то значит, экземпляр прикладного класса не инициализирован и нам его необходимо создать. Это должен выполнить некоторый новый метод Empty.

      procedure TMultiPointD2.Empty();

      begin

        Create( TPointsD2.Create( nil));

      end;

Итак, в результате наших улучшений кода "интеллектуального указателя" мы можем опробовать следующий вариант его использования:

    procedure TForm1.tbTestClick( Sender: TObject);

    var

//      VPT: TPointsD2;         // Больше не нужно!

      MPT: TMultiPointD2;

      PT: TPointD2;

    begin

//      VPT:=TPointsD2.Create;  // Больше не нужно!

//      MPT.Create( VPT);       // Больше не нужно!

      with PT do

        begin

          X:=0.1;

          Y:=221.3;

        end;

//      VPT.Add(PT);            // Больше не нужно!

      MPT.Value.Add(PT);

      with PT do

        begin

          X:=Random*100;

          Y:=221.3*Random;

        end;

      MPT.Value.Add(PT);

    End;

Закомментированные операторы нам больше не нужны! Похоже на чудо, но в данном случае абсолютно рациональная реальность… С этой суровой реальностью нам придется столкнуться в этой статье чуть позже…

Что может еще нас смущать в вышеприведенном примере? Посмотрим на следующие строки кода:

//.........................

//.........................

      MPT.Value.Add(PT);

//.........................

//.........................

      MPT.Value.Add(PT);

//.........................

//.........................

Конечно! Нас может смущать необходимость каждый раз обращаться к методам прикладного класса посредством вызова метода доступа  GetValue "интеллектуального указателя", в данном случае в следующем виде: MPT.Value.Add(PT).   Какие здесь существуют решения?

Собственно, вариантов здесь два:

  1. Реализовать собственные методы "интеллектуального указателя", инкапсулирующие вызовы GetValue в вышеприведенном виде.
  2. Использовать возможности, появившиеся  в Delphi 2007 по перегрузке операторов класса, а именно оператора неявного приведения типов Implicit.  

Для данного случая, в первом варианте можно предложить следующее решение:

      procedure TMultiPointD2.Add( PT: TPointD2);

      begin

        if(not PT.IsEmpty) then

          Value.Add( PT);

      end;

      procedure TMultiPointD2.Add( AValue: TMultiPointD2);

      begin

        Value.AddRange( AValue.Value);

      end;

      function TMultiPointD2.GetCount: integer;

      begin

        Result:=Value.Count;

      end;

      procedure TMultiPointD2.Clear;

      begin

        Value.Clear;

      end;

В случае использования второго варианта мы должны перегрузить операторы неявного приведения типов:

    class operator TMultiPointD2.Implicit( MPT: TMultiPointD2): TPointD2;

    begin

      Result:=MPT.Value;

    end;

    class operator TMultiPointD2.Implicit( MPT: TPointD2): TMultiPointD2;

    begin

      Result:=TMultiPointD2.Create(MPT);

    end;

    class operator TMultiPointD2.Implicit( PT: TPointD2): TMultiPointD2;

    begin

      Result.Clear();

      Result.Add( PT);

    end;

Таким образом, можно использовать следующий тестовый код:

    procedure TForm1.tbTestClick( Sender: TObject);

    var

      MPT: TMultiPointD2;

      PT: TPointD2;

    begin

      with PT do

        begin

          X:=0.1;

          Y:=221.3;

        end;

      MPT.Add(PT);

      for I:= 1 to 25 do

        begin

          with PT do

            begin

              X:=Random*100;

              Y:=221.3*Random;

            end;

          MPT.Add(PT);

      end;   

      for PT in MPT.Value do

        Memo1.Lines.Add(format('(X:%e,Y:%e)',[PT.X, PT.Y]));

    End;

В заключение можно расширить возможности работы с классом TMultiPointD2.

Например, добавим методы инкапсулирующие удаление элементов класса TMultiPointD2. И здесь мы сталкиваемся с первыми проблемами. Рассмотрим, что в этом случае получилось в следующем разделе.

Первый подводный камень. Удаление из массива.

Попробуем реализовать удаление элемента из массива 2-мерных точек. Пишем тестовую процедуру:

Объявление:

      procedure Delete( const AIndex: integer);

Реализация:

      procedure TMultiPointD2.Delete( const AIndex: integer);

      begin

        Value.Delete( AIndex);

      end;

Тест:

    procedure TForm1.tbTestDeleteClick( Sender: TObject);

    var

      MPT: TMultiPointD2<TPointsD2>;

      PT: TPointD2;

    begin

      with PT do

        begin

          X:=0.1;

          Y:=221.3;

        end;

      MPT.Add(PT);

      for I:= 1 to 25 do

        begin

          with PT do

            begin

              X:=Random*100;

              Y:=221.3*Random;

            end;

          MPT.Add(PT);

      end;   

      for PT in MPT.Value do

        Memo1.Lines.Add(format('(X:%e,Y:%e)',[PT.X, PT.Y]));

// Удаляем второй элемент массива.

      MPT.Delete( 1);   

    end;

Компилируем и нажимаем на форме соответствующую кнопочку, а затем получаем в ответ окошечко с примерно следующим текстом:

Сообщение об ошибке, возникающей при удалении элемента массива

После нескольких часов выстраивания всевозможных гипотез и работы с отладчиком и экспериментов с кодом модуля  Generics.Collections обнаруживается место которое вызывает крах:

procedure TList<T>.DoDelete(Index: Integer; Notification: TCollectionNotification);

var

  OldItem: T;

begin

  if (Index < 0) or (Index >= Count) then

    raise EArgumentOutOfRangeException.CreateRes(@sArgumentOutOfRange);

  OldItem:=FItems[Index];

  FItems[Index]:=Default(T); // Вот из-за этого все происходит!

  Dec(FCount);

  if Index <> Count then

  begin

    Move(FItems[Index + 1], FItems[Index], (Count - Index) * SizeOf(T));

    FillChar(FItems[Count], SizeOf(T), 0);

  end;

  Notify(oldItem, Notification);

end;

Заметим, что для массивов с элементами простых типов все работает нормально. Похоже, производитель не тестировал модуль Generics.Collections со сложными типами данных такими как "расширенные" записи.

Вариант исправления был найден досточно быстро:

procedure TList<T>.DoDelete(Index: Integer; Notification: TCollectionNotification);

var

  OldItem: T;

  DefItem: T;

begin

  if (Index < 0) or (Index >= Count) then

    raise EArgumentOutOfRangeException.CreateRes(@sArgumentOutOfRange);

  OldItem:=FItems[Index];

// Это ошибка?!

//  FItems[Index]:=Default(T);

// Так поправил!

  DefItem:=Default(T);

  FItems[Index]:=DefItem;

//----------------

  Dec(FCount);

  if Index <> Count then

  begin

    Move(FItems[Index + 1], FItems[Index], (Count - Index) * SizeOf(T));

    FillChar(FItems[Count], SizeOf(T), 0);

  end;

  Notify(oldItem, Notification);

end;

Тест стал проходить. Метод удаления элементов массива удалось получить.

Для дальнейшего нам необходимо определить метод позволяющий удалять элементы класса TMultiPointD2 по их значению. Т.е. нам необходим метод:

      procedure Remove( const AValue: TPointD2);

Ясно, что для этого классу TMultiPointD2 должно быть известно как сравнивать элементы типа TPointD2. Для этого случая разработчики модулей Generics.Default и Generics.Collections предусмотрели специальный класс TComparer<T>, имеющий специальны виртуальный метод function Compare( const Left, Right: T): integer; который можно переписать и реализовать собственный алгоритм сравнения элементов конкретного класса массива.

  IComparer<T> = interface

    function Compare(const Left, Right: T): Integer;

  end;

 

  IEqualityComparer<T> = interface

    function Equals(const Left, Right: T): Boolean;

    function GetHashCode(const Value: T): Integer;

  end;

 

  TComparison<T> = reference to function(const Left, Right: T): Integer;

 

  // Abstract base class for IComparer<T> implementations, and a provider

  // of default IComparer<T> implementations.

  TComparer<T> = class(TInterfacedObject, IComparer<T>)

  public

    class function Default: IComparer<T>;

    class function Construct(const Comparison: TComparison<T>): IComparer<T>;

    function Compare(const Left, Right: T): Integer; virtual; abstract;

  end;

Определения интерфейсов и классов, для реализации операций сравнения массивов в модуле Generics.Default.

Определим необходимый для нас класс-компаратор TLinePointsComparer:

    IComparerPoint      = IComparer<TPointD2>;

    TComparerPoint      = class(TComparer<TPointD2>, IComparerPoint)

    public

      constructor Create;

    end;

    TLinePointsComparer = class(TComparerPoint)

    public

      function Compare( const Left, Right: TPointD2): integer; override;

    end;

В самом названии, мы выбрали алгоритм упорядочения точек массива - строчный: PTA < PTB, если PTA.X < PTB.X, или если  PTA.X = PTB.X и PTA.Y < PTB.Y. Точки считаются эквивалентными если PTA.X = PTB.X и PTA.Y = PTB.Y. Реализация этого алгоритма может быть следующей:

      function TLinePointsComparer.Compare( const Left, Right: TPointD2): integer;

      begin

        if(Abs(Left.X-Right.X)<=MinSingle) then

          begin

            if(Abs(Left.Y-Right.Y)<=MinSingle) then

              Exit( +0);

            if(Left.Y < Right.Y) then

              Exit( -1);

            if(Left.Y > Right.Y) then

              Exit( +1);

        end;

        if(Left.X < Right.X) then

          Exit( -1);

        Exit( +1);

      end;

Какой еще шаг нам предстоит выполнить, чтобы мы добились желаемого?

Вернемся к определению класса TPointsD2. Обратим свое внимание на закомментированную строку в его определении:

//      class function GetComparer: TComparerPoint; virtual;

Нам ее стоит теперь "вернуть к жизни" и написать ее реализацию:

       class function GetComparer: TComparerPoint; virtual;

//.........................

//.........................

       class function TPointsD2.GetComparer: TComparerPoint;

       begin

         Result:=TLinePointsComparer.Create;

       End;

Где же этот метод нужно использовать? Вспомним реализацию метода Empty класса  TMultiPointD2 которую мы предлагали ранее.

      procedure TMultiPointD2.Empty();

      begin

        Create( TPointsD2.Create( nil));

      end;

Дело в том, что передаваемым параметром конструктора класса TPointsD2 как раз и является экземпляр класса-компаратора. Поэтому метод Empty можно переписать следующим образом:

      procedure TMultiPointD2.Empty;

      begin

        Create( TPointsD2.Create( TLinePointsComparer.Create));

      end;

или

      procedure TMultiPointD2.Empty;

      begin

        Create( TPointsD2.Create( TPointsD2.GetComparer));

      end;

Второй вариант нам может показаться более предпочтительным, поскольку предоставляет возможность дальнейшей параметризации класса TMultiPointD2.

После этого можно протестировать метод Remove с помощью следующей процедуры

    procedure TForm1.tbTestDeleteClick( Sender: TObject);

    var

      MPT: TMultiPointD2<TPointsD2>;

      PT: TPointD2;

    begin

      with PT do

        begin

          X:=0.1;

          Y:=221.3;

        end;

      MPT.Add(PT);

      for I:= 1 to 25 do

        begin

          with PT do

            begin

              X:=Random*100;

              Y:=221.3*Random;

            end;

          MPT.Add(PT);

      end;   

      for PT in MPT.Value do

        Memo1.Lines.Add(format('(X:%e,Y:%e)',[PT.X, PT.Y]));

// Удаляем третий элемент массива.

      MPT.Remove(MPT.Value[3]);   

    end;

Все работает.

Улучшение класса TMultiPointD2

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

      function Equals( AValue: TMultiPointD2): boolean;

      function IsEmpty: boolean;

      function Min: TPointD2; overload;

      function Max: TPointD2; overload;

      function Min( MPT: TMultiPointD2): TPointD2; overload;

      function Max( MPT: TMultiPointD2): TPointD2; overload;

      procedure Offset( const PT: TPointD2);

      class operator Implicit( MPT: TPointsD2): TMultiPointD2;

      class operator Implicit( MPT: TMultiPointD2): TPointsD2;

      class operator Implicit( PT: TPointD2): TMultiPointD2;

      class operator Add( MPT: TMultiPointD2; PT: TPointD2): TMultiPointD2; overload;

      class operator Add( PT: TPointD2; MPT: TMultiPointD2): TMultiPointD2; overload;

      class operator Add( MPTA, MPTB: TMultiPointD2): TMultiPointD2; overload;

      class operator Subtract( MPT: TMultiPointD2; PT: TPointD2): TMultiPointD2; overload;

      class operator Subtract( MPTA, MPTB: TMultiPointD2): TMultiPointD2; overload;

      class operator Multiply( PT: TPointD2; MPT: TMultiPointD2): TPointD2; overload;

      class operator Multiply( MPT: TMultiPointD2; PT: TPointD2): TPointD2; overload;

      class operator Multiply( MPTA, MPTB: TMultiPointD2): TMultiPointD2; overload;

      class operator LessThan( PT: TPointD2; MPT: TMultiPointD2): boolean; overload;

      class operator GreaterThan( MPT: TMultiPointD2; PT: TPointD2): boolean; overload;

      class operator LessThanOrEqual( PT: TPointD2; MPT: TMultiPointD2): boolean; overload;

      class operator GreaterThanOrEqual( MPT: TMultiPointD2; PT: TPointD2): boolean; overload;

  1. Описание введенных методов и операторов класса TMultiPointD2

Конструкция

Описание

function Equals( AValue: TMultiPointD2): boolean;

Инкапсулирует стандартный метод сравнение указателей на класс  TPointD2.

function IsEmpty: boolean;

Метод проверяет является ли мультиточка "пустой".

function Min: TPointD2;

Вычисляет "минимальную" точку из текущей мультиточки.

function Max: TPointD2;

Вычисляет "максимальную" точку из текущей мультиточки

function Min( MPT: TMultiPointD2): TPointD2;

Вычисляет "минимальную" точку из текущей мультиточки и мультиточки MPT.

function Max( MPT: TMultiPointD2): TPointD2;

Вычисляет "максимальную" точку из текущей мультиточки и мультиточки MPT.

class operator Add( MPT: TMultiPointD2; PT: TPointD2): TMultiPointD2

Добавление к мультиточке MPT точки MP. Синтаксис: X:=MPT+PT.  

class operator Add( PT: TPointD2; MPT: TMultiPointD2): TMultiPointD2; overload;

Добавление к мультиточке MPT точки MP. Синтаксис: X:=PT+MPT.

class operator Add( MPTA, MPTB: TMultiPointD2): TMultiPointD2

Соединение двух мультиточек MPTA и MPTB в указанном порядке. Синтаксис: X:=MPTA+MPTB.

class operator Subtract( MPT: TMultiPointD2; PT: TPointD2): TMultiPointD2;

Удаление из мультиточки MPT точки PT. Аналогично: X:=MPT-PT.

class operator Subtract( MPTA, MPTB: TMultiPointD2): TMultiPointD2;

Удаление из мультиточки MPTA мультиточки MPTB. Синтаксис: X:=MPTA-MPTB.

class operator Multiply( PT: TPointD2; MPT: TMultiPointD2): TPointD2;

Определение пересечения точки PT и мультиточки MPT. Если точки PT содержится в мультиточке MPT, то возвращается эта точка PT; иначе - возвращается пустая точка. Синтаксис: X:=PT*MPT.

class operator Multiply( MPT: TMultiPointD2; PT: TPointD2): TPointD2;

Аналогично предыдущему оператору. Добавлено для обеспечения симметричности оператора.  Синтаксис: X:=MPT*PT.

class operator LessThan( PT: TPointD2; MPT: TMultiPointD2): boolean;

Метод проверяет принадлежность точки PT MPT. Синтаксис:  B:=PT < MPT.

class operator GreaterThan( MPT: TMultiPointD2; PT: TPointD2): boolean;

Метод проверяет принадлежность точки PT MPT. Синтаксис:  B:=MPT > PT.

class operator LessThanOrEqual( PT: TPointD2; MPT: TMultiPointD2): boolean;

Метод проверяет принадлежность точки PT MPT. Синтаксис:  B:=PT <= MPT.

class operator GreaterThanOrEqual( MPT: TMultiPointD2; PT: TPointD2): boolean;

Метод проверяет принадлежность точки PT MPT. Синтаксис:  B:=MPT >= PT.

Рассмотрим реализацию этих методов и операторов (см. следующую врезку).

      function TMultiPointD2.Equals( AValue: TMultiPointD2): boolean;

      begin

        Result:=Value.Equals( AValue.Value);

      end;

      class operator TMultiPointD2.Implicit( MPT: TMultiPointD2): TPointsD2;

      begin

        Result:=MPT.Value;

      end;

      class operator TMultiPointD2.Implicit( MPT: TPointsD2): TMultiPointD2;

      begin

        Result:=TMultiPointD2.Create(MPT);

      end;

      class operator TMultiPointD2.Implicit( PT: TPointD2): TMultiPointD2;

      begin

        Result.Add( PT);

      end;

      function TMultiPointD2.IsEmpty: boolean;

      begin

        Result:=(Count = 0);

      end;

      function TMultiPointD2.Min: TPointD2;

      var

        PT: TPointD2;

      begin

        Result.Empty;

        if(not IsEmpty) then

          begin

            Result:=Self[0];

            for PT in Value do

              Result:=Result.Min( PT);

        end;

      end;

      function TMultiPointD2.Min( const MPT: TMultiPointD2): TPointD2;

      var

        MPTA,

        MPTB: TPointD2;

      begin

        MPTA:=Self.Min;

        MPTB:=MPT.Min;

        Result:=MPTA.Min( MPTB);

      end;

      function TMultiPointD2.Max: TPointD2;

      var

        PT: TPointD2;

      begin

        Result.Empty;

        if(not IsEmpty) then

          begin

            Result:=Self[0];

            for PT in Value do

              Result:=Result.Max( PT);

        end;

      end;

      function TMultiPointD2.Max( const MPT: TMultiPointD2): TPointD2;

      var

        MPTA,

        MPTB: TPointD2;

      begin

        MPTA:=Self.Max;

        MPTB:=MPT.Max;

        Result:=MPTA.Max( MPTB);

      end;

      class operator TMultiPointD2.LessThan( const PT: TPointD2; const MPT: TMultiPointD2): boolean;

      begin

        Result:=(MPT.Value.IndexOf( PT) > -1);

      end;

      class operator TMultiPointD2.LessThanOrEqual( const PT: TPointD2; const MPT: TMultiPointD2): boolean;

      begin

        Result:=(PT < MPT);

      end;

      class operator TMultiPointD2.GreaterThan( const MPT: TMultiPointD2; const PT: TPointD2): boolean;

      begin

        Result:=(PT < MPT);

      end;

      class operator TMultiPointD2.GreaterThanOrEqual(const MPT: TMultiPointD2; const PT: TPointD2): boolean;

      begin

        Result:=(PT <= MPT);

      end;

      function TMultiPointD2.Copy: TMultiPointD2;

      begin

        if(not Result.Equals( Self)) then

          begin

            Result.Clear;

            Result.Add(Self);

        end;

      end;

      procedure TMultiPointD2.Add( const PT: TPointD2);

      begin

        if(not PT.IsEmpty) then

          Value.Add( PT);

      end;

      procedure TMultiPointD2.Add( const AValue: TMultiPointD2);

      begin

        Value.AddRange( AValue.Value);

      end;

      function TMultiPointD2.GetCount: integer;

      begin

        Result:=Value.Count;

      end;

      class operator TMultiPointD2.Add( const MPT: TMultiPointD2; const PT: TPointD2): TMultiPointD2;

      begin

        if(not Result.Equals( MPT)) then      // X:=A+PT

          begin

            Result.Clear;

            Result.Value.AddRange(MPT);

        end;

        Result.Add(PT);

      end;

      class operator TMultiPointD2.Add( const PT: TPointD2; const MPT: TMultiPointD2): TMultiPointD2;

      begin

        if(not Result.Equals( MPT)) then      // X:=PT+A;

          Result.Clear;

        Result.Value.Insert( 0, PT);

        if(not Result.Equals( MPT)) then      // not A:=PT+A;

          Result.Value.AddRange(MPT);

      end;

      class operator TMultiPointD2.Add( const MPTA, MPTB: TMultiPointD2): TMultiPointD2;

      begin

        if(Result.Equals( MPTA)) then

          begin

            if(not Result.Equals( MPTB)) then // A:=A+B.

// Случай A:=A+A не обрабатывается! Это может привести к зацикливанию!

              Result.Value.AddRange(MPTB)

          end

        else if(Result.Equals( MPTB)) then    // B:=A+B.

          Result.Value.InsertRange( 0, MPTA)

        else

          begin                               // X:=A+B.

            Result.Clear;

            Result.Value.AddRange(MPTA);

            Result.Value.AddRange(MPTB);

        end;

      end;

      class operator TMultiPointD2.Subtract( const MPT: TMultiPointD2; const PT: TPointD2): TMultiPointD2;

      begin

        if(not Result.Equals( MPT)) then      // X:=A-PT.

          begin

            Result.Clear;

            Result.Value.AddRange( MPT);

        end;

        Result.Delete(PT);                    // X:=A-PT или A:=A-PT.

      end;

      class operator TMultiPointD2.Subtract( const MPTA, MPTB: TMultiPointD2): TMultiPointD2;

      var

        PT: TPointD2;

        VP: TMultiPointD2;

      begin

        if(Result.Equals( MPTA)) then

          begin

            if(not Result.Equals( MPTB)) then // A:=A-B.

              begin

                for PT in MPTB.Value do

                  Result.Delete( PT);

            end;

          end

        else if(Result.Equals( MPTB)) then    // B:=A-B.

          begin

            VP:=MPTA.Copy();

            VP:=VP - MPTB;

            Result:=VP.Copy();

          end

        else

          begin                               // X:=A-B.

            Result:=MPTA.Copy();

            for PT in MPTB.Value do

              Result.Delete( PT);

        end;

      end;

      class operator TMultiPointD2.Multiply( const PT: TPointD2; const MPT: TMultiPointD2): TPointD2;

      begin

        Result.Empty;

        if((not (PT.IsEmpty or MPT.IsEmpty)) and (PT < MPT)) then

          Result:=PT;

      end;

      class operator TMultiPointD2.Multiply( const MPT: TMultiPointD2; const PT: TPointD2): TPointD2;

      begin

        Result:=PT*MPT;

      end;

      class operator TMultiPointD2.Multiply( const MPTA, MPTB: TMultiPointD2): TMultiPointD2;

      var

        PT: TPointD2;

      begin

        Result.Empty;

        for PT in MPTA.Value do

          Result.Add(PT*MPTB);

      end;

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

Казалось бы, материал, изложенный в этом разделе, имеет косвенное отношение к  объявленной теме статьи. Украшательство - да и только… и т.д. и т.п. Автору самому не хотелось его излагать. Однако код, приведенный в текущем разделе, и параметризация классов в стиле generic связаны с решением одной проблемы, о которой я хотел бы рассказать в следующем разделе.

У всего, что мы до сих пор сделали, имеется одна неприметная, но серьезная проблема, о которой я упоминал выше. Она связана с тем, что при данном варианте реализации метода удаления по значению фактически мы можем использовать лишь один фиксированный способ сравнения (упорядочивания) элементов экземпляра класса TMultiPointD2 типа TPointD2. Нам необходимо каким-то образом добиться параметризации созданных классов относительно класса-компаратора. Иначе какие-либо достоинства использования "интеллектуальных указателей" с точки зрения автора статьи отсутствуют. Решению этой задачи будет посвящен следующий раздел нашей статьи.

Параметризация сравнения элементов

Для чего это нужно?

Предположим, что кроме класса "мультиточка" TMultiPointD2 нам необходимо реализовать класс "полигон" TPolygonD2 (который является 2-мерной областью, ограниченной несамопересекающейся ломанной, строго говоря, включая и саму границу).  Другим отличием является отсутствие в полигоне эквивалентных точек - требуется уникальность элементов массива по содержанию. При этом нам необходимо реализовать его не собственно как класс, а в виде "интеллектуального указателя", с соответствующим набором методов и перегруженных операторов.

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

Для метода удаления такой проблемы нет. Метод удаления по значению использует лишь отношение эквивалентности двух элементов. Собственно, какая разница для метода удаления, какой класс-компаратор будет использован? Для него достаточно того, что реализация разных алгоритмов сравнения для отношения эквивалентности будет выдавать адекватный результат: два элемента (передаваемый образец и удаляемый элемент массива) эквивалентные (тождественные) при одном способе сравнения должны оставаться эквивалентными (тождественными) и при другом способе сравнения, с условием, что они имеют одинаковое содержимое - одинаковые значения. Строго формально это не так.

Надо сразу заметить, что эта проблема вытекает из реализации разработчиками модуля Generics.Collections  метода передачи экземпляра класса-компаратора, используемого классом-контейнером TList<T> по умолчанию. Подменить его можно только на время проведения какой-либо специфической сортировки, снапример, с использованием перегруженного метода procedure Sort(const AComparer: IComparer<T>). Никаких других способов передачи компаратора в классе TList<T> нет. При других способах реализации класса-контейнера нам, возможно, не понадобилось бы проходить весь этот путь.

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

Что же можно предложить? Во-первых, использование делегирования, в том числе с использованием с использованием анонимных методов. Во-вторых,  использование параметризации класса TMultiPointD2 классами-компараторами или прикладными классами….

Делегирование

Идея данного решения проста: ввести в определение TMultiPointD2 классовую переменную (и связанное с ней свойство) типа ссылки на класс-компаратор и перед первым использованием данного "интеллектуального указателя" выполнить ее инициализацию.

Сделаем это как показано на следующих врезках:

  1. Объявляем ссылки на классы-компараторы:

    IComparerPoint      = IComparer<TPointD2>;

    TComparerPoint      = class(TComparer<TPointD2>, IComparerPoint)

    public

      constructor Create;

    end;

    TClassComparerPoint = class of TComparerPoint;

    TLinePointsComparer = class(TComparerPoint)

    public

      function Compare( const Left, Right: TPointD2): integer; override;

    end;

    TClassLinePointsComparer = class of TLinePointsComparer;

    TPolygonPointsComparer = class(TComparerPoint)

    public

      function Compare( const Left, Right: TPointD2): integer; override;

    end;

    TClassPolygonPointsComparer = class of TPolygonPointsComparer;

  1. Реализуем переписанный метод в классе наследнике

      function TPolygonPointsComparer.Compare( const Left, Right: TPointD2): integer;

      var

        AL, AR: single;

        DL, DR: single;

      begin

        AL:=Left.Angle;

        AR:=Right.Angle;

        if(Abs(AL-AR) <= MinSingle) then

          begin

            DL:=Left.Distance;

            DR:=Right.Distance;

            if(Abs(DL-DR) <= MinSingle) then

              exit(+0);

            if(DL < DR) then

              exit(-1)

            else

              exit(+1);

        end;

        if(AL < AR) then

          exit(-1);

        exit(+1);

      end;

  1. Вводим необходимые классовые переменную и связанное с ней свойство:

    TMultiPointD2 = record

    strict private

//........................................

//........................................

        FValue: TPointsD2;

        FFreeTheValue: IInterface;

        FComparer: TClassComparerPoint;

//........................................

    public

//........................................

//........................................

        property Comparer: TClassComparerPoint read FComparer write FComparer;

    end;

  1. Переопределяем метод Empty:

      procedure TMultiPointD2.Empty;

      begin

        Create( TPointsD2.Create( FComparer.Create));

      end;

  1. Проверяем. Для этого нам необходимо реализовать следующую процедуру:

procedure TForm4.Button7Click(Sender: TObject);

      var

        I: integer;

        MPTR: TMultiPointD2; // "мультиточка"

        MPTP: TMultiPointD2; // "полигон".

        PT: TPointD2;

        PTB: TPointD2;

        PTC: TPointD2;

      begin

// Используем строчную сортировку.

        MPTR.Comparer:=TLinePointsComparer;

// Используем "полигональную" сортировку.

        MPTP.Comparer:=TPolygonPointsComparer;

        for I:= 1 to 25 do

          begin

            PT.X:=100.0*Random;

            PT.Y:=100.0*Random;

            MPTR.Add( PT);

        end;

        MPTP:=MPTR.Copy;

        Memo1.Lines.Add('До сортировки: мультиточка.');

        for PT in MPTR.Value do

          Memo1.Lines.Add(format('(X:%e, Y:%e)',[PT.X, PT.Y]));

        Memo1.Lines.Add('До сортировки: полигон.');

        for PT in MPTP.Value do

          Memo1.Lines.Add(format('(X:%e, Y:%e)',[PT.X, PT.Y]));

//================================.

// Сортируем "мультиточку".

        MPTR.Value.Sort;

//================================.

//Сортируем "полигон".

        PTC:=MPTP.Center;

        PTB:=PTC.Invert;

        MPTP.Offset(PTB);

          MPTP.Value.Sort;

        MPTP.Offset(PTC);

//================================.

        Memo1.Lines.Add(''После сортировки: мультиточка.');

        for PT in MPTR.Value do

          Memo1.Lines.Add(format('(X:%e, Y:%e)',[PT.X, PT.Y]));

        Memo1.Lines.Add('После сортировки: полигон.');

        for PT in MPTP.Value do

          Memo1.Lines.Add(format('(X:%e, Y:%e)',[PT.X, PT.Y]));

      end;

Результат работы процедуры можно посмотреть в следующей таблице:

  1. Результаты вывода тестовой процедуры.

До сортировки

Мультиточка

Полигон

(X:2,328306E-008, Y:3,137994E+000)

(X:2,328306E-008, Y:3,137994E+000)

(X:8,610484E+001, Y:2,025809E+001)

(X:8,610484E+001, Y:2,025809E+001)

(X:2,729212E+001, Y:6,716544E+001)

(X:2,729212E+001, Y:6,716544E+001)

(X:3,186912E+001, Y:1,617954E+001)

(X:3,186912E+001, Y:1,617954E+001)

(X:3,722383E+001, Y:4,256737E+001)

(X:3,722383E+001, Y:4,256737E+001)

(X:8,201215E+000, Y:4,747940E+001)

(X:8,201215E+000, Y:4,747940E+001)

(X:7,056932E+000, Y:8,408544E+001)

(X:7,056932E+000, Y:8,408544E+001)

(X:5,972423E+000, Y:2,932965E+001)

(X:5,972423E+000, Y:2,932965E+001)

(X:9,172846E+001, Y:3,679064E+001)

(X:9,172846E+001, Y:3,679064E+001)

(X:7,746649E+001, Y:3,279255E+001)

(X:7,746649E+001, Y:3,279255E+001)

(X:6,976749E+001, Y:8,441709E+001)

(X:6,976749E+001, Y:8,441709E+001)

(X:7,179828E+001, Y:3,066413E+001)

(X:7,179828E+001, Y:3,066413E+001)

(X:1,626258E+001, Y:3,294964E+001)

(X:1,626258E+001, Y:3,294964E+001)

(X:4,660204E+001, Y:2,466536E+001)

(X:4,660204E+001, Y:2,466536E+001)

(X:8,256764E+001, Y:2,790294E+001)

(X:8,256764E+001, Y:2,790294E+001)

(X:4,817663E+001, Y:1,491849E+001)

(X:4,817663E+001, Y:1,491849E+001)

(X:8,743350E+001, Y:2,872941E+001)

(X:8,743350E+001, Y:2,872941E+001)

(X:7,727532E+001, Y:9,764598E+001)

(X:7,727532E+001, Y:9,764598E+001)

(X:4,925279E+001, Y:8,879360E+001)

(X:4,925279E+001, Y:8,879360E+001)

(X:8,272766E+001, Y:2,029689E+000)

(X:8,272766E+001, Y:2,029689E+000)

(X:1,410562E+001, Y:1,435042E+001)

(X:1,410562E+001, Y:1,435042E+001)

(X:5,008077E+001, Y:2,167585E+000)

(X:5,008077E+001, Y:2,167585E+000)

(X:5,929344E+001, Y:9,648999E-001)

(X:5,929344E+001, Y:9,648999E-001)

(X:7,744779E+001, Y:6,506576E+001)

(X:7,744779E+001, Y:6,506576E+001)

(X:7,704886E+001, Y:7,081069E+001)

(X:7,704886E+001, Y:7,081069E+001)

После сортировки

Мультиточка

Полигон

(X:2,328306E-008, Y:3,137994E+000)

(X:1,626258E+001, Y:3,294964E+001)

(X:5,972423E+000, Y:2,932965E+001)

(X:5,972423E+000, Y:2,932965E+001)

(X:7,056932E+000, Y:8,408544E+001)

(X:1,410562E+001, Y:1,435042E+001)

(X:8,201215E+000, Y:4,747940E+001)

(X:0,000000E+000, Y:3,137992E+000)

(X:1,410562E+001, Y:1,435042E+001)

(X:3,186912E+001, Y:1,617954E+001)

(X:1,626258E+001, Y:3,294964E+001)

(X:4,660204E+001, Y:2,466536E+001)

(X:2,729212E+001, Y:6,716544E+001)

(X:4,817663E+001, Y:1,491849E+001)

(X:3,186912E+001, Y:1,617954E+001)

(X:5,008077E+001, Y:2,167583E+000)

(X:3,722383E+001, Y:4,256737E+001)

(X:5,929344E+001, Y:9,649009E-001)

(X:4,660204E+001, Y:2,466536E+001)

(X:8,272766E+001, Y:2,029689E+000)

(X:4,817663E+001, Y:1,491849E+001)

(X:8,610484E+001, Y:2,025809E+001)

(X:4,925279E+001, Y:8,879360E+001)

(X:7,179828E+001, Y:3,066413E+001)

(X:5,008077E+001, Y:2,167585E+000)

(X:8,256764E+001, Y:2,790294E+001)

(X:5,929344E+001, Y:9,648999E-001)

(X:8,743350E+001, Y:2,872941E+001)

(X:6,976749E+001, Y:8,441709E+001)

(X:7,746649E+001, Y:3,279255E+001)

(X:7,179828E+001, Y:3,066413E+001)

(X:9,172846E+001, Y:3,679064E+001)

(X:7,704886E+001, Y:7,081069E+001)

(X:7,744779E+001, Y:6,506576E+001)

(X:7,727532E+001, Y:9,764598E+001)

(X:7,704886E+001, Y:7,081069E+001)

(X:7,744779E+001, Y:6,506576E+001)

(X:7,727532E+001, Y:9,764598E+001)

(X:7,746649E+001, Y:3,279255E+001)

(X:6,976749E+001, Y:8,441709E+001)

(X:8,256764E+001, Y:2,790294E+001)

(X:4,925279E+001, Y:8,879360E+001)

(X:8,272766E+001, Y:2,029689E+000)

(X:2,729212E+001, Y:6,716544E+001)

(X:8,610484E+001, Y:2,025809E+001)

(X:7,056934E+000, Y:8,408544E+001)

(X:8,743350E+001, Y:2,872941E+001)

(X:3,722383E+001, Y:4,256737E+001)

(X:9,172846E+001, Y:3,679064E+001)

(X:8,201213E+000, Y:4,747940E+001)

Сразу обратим внимание читателя на следующий момент: нам потребовалось изменить реализацию процедуры procedure TMultiPointD2.Offset( const PT: TPointD2) (см. следующую врезку).

      procedure TMultiPointD2.Offset( const PT: TPointD2);

      var

        PTA,

        PTB: TPointD2;

        MPT: TMultiPointD2;

      begin

        MPT.Comparer:=Comparer; // необходимо ввести следующее присваивание!

        for PTA in Value do

          begin

            PTB.X:=PTA.X+PT.X;

            PTB.Y:=PTA.Y+PT.Y;

            MPT.Add(PTB);

        end;

        Self.Clear;

        Self.Add(MPT);

      end;

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

Казалось бы, можно вместо этого просто изменить процедуру Empty:

      procedure TMultiPointD2.Empty;

      begin

        if(not Assigned(FComparer)) then

          FComparer:=TLinePointsComparer; // Компаратор по умолчанию!

        Create( TPointsD2.Create( FComparer.Create));

      end;

И это возможно наиболее правильное решение для данного варианта? Но оно не работает, поскольку FComparer может быть неинициализировано nil (и его негде инициализировать).

Решение может быть следующим:

      procedure TMultiPointD2.Empty;

      begin

        if( FComparer <> Default(TClassComparerPoint)) then

          FComparer:=TLinePointsComparer;

        Create( TPointsD2.Create( FComparer.Create));

      end;

Преимущества и недостатки этого варианта можно обсуждать еще очень долго...

Параметризация классами-компараторами

Главная идея этого варианта состоит в том, чтобы написать некоторый родительский класс, наследуемый скажем от TList<TPointD2>, определяющий основные методы и свойства его "семейства". А затем, параметризовать "интеллектуальный указатель" этого "семейства" на основе полученного таким образом базового класса. Посмотрим как это сделать.

1. Определяем базовый прикладной класс и его наследника, с минимальным набором методов

    TPointsD2 = class(TList<TPointD2>)

    public

// Некоторый псевдоконструктор.

      function Empty: TPointsD2; virtual;

      destructor Destroy; override;

      function IsEmpty: boolean;

      procedure Sort; virtual;

      procedure Add( const Value: TPointD2); virtual;

      procedure Offset( const PT: TPointD2); virtual;

      function Center: TPointD2;

    end;

    TPolygonPointsD2 = class(TPointsD2)

    public

      function Empty: TPointsD2; override;

      destructor Destroy; override;

      procedure Sort; override;

      procedure Add( const Value: TPointD2); override;

    end;

2. Реализуем методы этих классов:

// TPointsD2.

      function TPointsD2.Empty: TPointsD2;

      begin

        Result:=TPointsD2.Create( TLinePointsComparer.Create);

      end;

      function TPointsD2.IsEmpty: boolean;

      begin

        Result:=(Count = 0);

      end;

      destructor TPointsD2.Destroy;

      begin

        inherited Destroy;

      end;

      function TPointsD2.Center: TPointD2;

      var

        PT: TPointD2;

      begin

        Result.Empty;

        if(not IsEmpty) then

          begin

            Result.X:=0.0;

            Result.Y:=0.0;

            for PT in Self do

              begin

                Result.X:=Result.X+PT.X;

                Result.Y:=Result.Y+PT.Y;

            end;

            Result.X:=Result.X/Count;

            Result.Y:=Result.Y/Count;

        end;

      end;

      procedure TPointsD2.Offset( const PT: TPointD2);

      var

        TPT: TPointD2;

      begin

        for TPT in Self do

          begin

            TPT.X:=TPT.X+PT.X;

            TPT.Y:=TPT.Y+PT.Y;

        end;

      end;

      procedure TPointsD2.Sort;

      begin

        inherited Sort;

      end;

      procedure TPointsD2.Add( const Value: TPointD2);

      begin

        inherited Add( Value);

      end;

//================================================================

// TPolygonPointsD2.

      function TPolygonPointsD2.Empty: TPointsD2;

      begin

        Result:=TPointsD2.Create( TPolygonPointsComparer.Create);

      end;

      destructor TPolygonPointsD2.Destroy;

      begin

        inherited Destroy;

      end;

      procedure TPolygonPointsD2.Sort;

      var

        PTB: TPointD2;

        PTC: TPointD2;

      begin

        PTC:=Center;

        PTB:=PTC.Invert;

        Offset( PTB);

        inherited Sort;

        Offset(PTC);

      end;

      procedure TPolygonPointsD2.Add( const Value: TPointD2);

      begin

        inherited Add( Value);

// Пересортируем после добавления!

        Sort;

      end;

3. Определяем "интеллектуальный указатель":

  1. Объявление TMultiPointD2<TPoints: TPointsD2>

    TMultiPointD2<TPoints: TPointsD2> = record

    strict private

        FValue: TPointsD2;

        FFreeTheValue: IInterface;

      function GetValue: TPointsD2;

    private

      type

        TFreeTheValue = class (TInterfacedObject)

        private

            FObjectToFree: TObject;

        public

          constructor Create( AObjectToFree: TObject);

          destructor Destroy; override;

        end;

    private

      function GetCount: integer;

      function GetPoint( const Index: integer): TPointD2;

      procedure SetPoint( const Index: integer; const AValue: TPointD2);

    public

      constructor Create( AValue: TPointsD2); overload;

      procedure Empty; overload;

      function IsEmpty: boolean;

      function Equals( AValue: TMultiPointD2<TPoints>): boolean;

      procedure Clear;

      procedure Add( const PT: TPointD2); overload;

      procedure Add( const AValue: TMultiPointD2<TPoints>); overload;

      function Copy: TMultiPointD2<TPoints>;

      procedure Delete( const AValue: TPointD2);

      procedure Remove( const AValue: TPointD2);

      function Min: TPointD2; overload;

      function Max: TPointD2; overload;

      function Min( const MPT: TMultiPointD2<TPoints>): TPointD2; overload;

      function Max( const MPT: TMultiPointD2<TPoints>): TPointD2; overload;

      procedure Offset( const PT: TPointD2);

      class operator Implicit( const MPT: TPointsD2): TMultiPointD2<TPoints>;

      class operator Implicit( const MPT: TMultiPointD2<TPoints>): TPointsD2;

      class operator Implicit( const PT: TPointD2): TMultiPointD2<TPoints>;

      class operator Add( const MPT: TMultiPointD2<TPoints>; const PT: TPointD2): TMultiPointD2<TPoints>; overload;

      class operator Add( const PT: TPointD2; const MPT: TMultiPointD2<TPoints>): TMultiPointD2<TPoints>; overload;

      class operator Add( const MPTA, MPTB: TMultiPointD2<TPoints>): TMultiPointD2<TPoints>; overload;

      class operator Subtract( const MPT: TMultiPointD2<TPoints>; const PT: TPointD2): TMultiPointD2<TPoints>; overload;

      class operator Subtract( const MPTA, MPTB: TMultiPointD2<TPoints>): TMultiPointD2<TPoints>; overload;

      class operator Multiply( const PT: TPointD2; const MPT: TMultiPointD2<TPoints>): TPointD2; overload;

      class operator Multiply( const MPT: TMultiPointD2<TPoints>; const PT: TPointD2): TPointD2; overload;

      class operator Multiply( const MPTA, MPTB: TMultiPointD2<TPoints>): TMultiPointD2<TPoints>; overload;

      class operator LessThan( const PT: TPointD2; const MPT: TMultiPointD2<TPoints>): boolean; overload;

      class operator GreaterThan( const MPT: TMultiPointD2<TPoints>; const PT: TPointD2): boolean; overload;

      class operator LessThanOrEqual( const PT: TPointD2; const MPT: TMultiPointD2<TPoints>): boolean; overload;

      class operator GreaterThanOrEqual( const MPT: TMultiPointD2<TPoints>; const PT: TPointD2): boolean; overload;

      function Center: TPointD2;

        property Value: TPointsD2 read GetValue;

        property Count: integer read GetCount;

        property Points[ const Index: integer]: TPointD2 read GetPoint write SetPoint; default;

        property Comparer: TClassComparerPoint read FComparer write FComparer;

    end;

4. Реализуем методы TMultiPointD2<TPoints: TPointsD2>:

  1. Реализация методов TMultiPointD2<TPoints: TPointsD2>

      constructor TMultiPointD2<TPoints>.Create( AValue: TPointsD2);

      begin

        FValue:=AValue;

        FFreeTheValue:=TFreeTheValue.Create( FValue);

      end;

      procedure TMultiPointD2<TPoints>.Empty;

      begin

        Create( TPoints.Empty);

      end;

      function TMultiPointD2<TPoints>.IsEmpty: boolean;

      begin

        Result:=(Count = 0);

      end;

      function TMultiPointD2<TPoints>.Center: TPointD2;

      var

        PT: TPointD2;

      begin

        Result.Empty;

        if(not IsEmpty) then

          begin

            Result.X:=0.0;

            Result.Y:=0.0;

            for PT in Value do

              begin

                Result.X:=Result.X+PT.X;

                Result.Y:=Result.Y+PT.Y;

            end;

            Result.X:=Result.X/Count;

            Result.Y:=Result.Y/Count;

        end;

      end;

      procedure TMultiPointD2<TPoints>.Clear;

      begin

        Value.Clear;

      end;

      function TMultiPointD2<TPoints>.GetValue: TPointsD2;

      begin

        if( not Assigned(FFreeTheValue)) then

          Empty;

        Result:=FValue;

      end;

      function TMultiPointD2<TPoints>.GetCount: integer;

      begin

        Result:=Value.Count;

      end;

      function TMultiPointD2<TPoints>.GetPoint( const Index: integer): TPointD2;

      begin

        Result:=Self.Value[ Index];

      end;

      procedure TMultiPointD2<TPoints>.SetPoint( const Index: integer; const AValue: TPointD2);

      begin

        Self.Value[ Index]:=AValue;

      end;

      function TMultiPointD2<TPoints>.Equals( AValue: TMultiPointD2<TPoints>): boolean;

      begin

        Result:=Value.Equals( AValue.Value);

      end;

      function TMultiPointD2<TPoints>.Min: TPointD2;

      var

        PT: TPointD2;

      begin

        Result.Empty;

        if(not IsEmpty) then

          begin

            Result:=Self[0];

            for PT in Value do

              Result:=Result.Min( PT);

        end;

      end;

      function TMultiPointD2<TPoints>.Max: TPointD2;

      var

        PT: TPointD2;

      begin

        Result.Empty;

        if(not IsEmpty) then

          begin

            Result:=Self[0];

            for PT in Value do

              Result:=Result.Max( PT);

        end;

      end;

      function TMultiPointD2<TPoints>.Min( const MPT: TMultiPointD2<TPoints>): TPointD2;

      var

        MPTA,

        MPTB: TPointD2;

      begin

        MPTA:=Self.Min;

        MPTB:=MPT.Min;

        Result:=MPTA.Min( MPTB);

      end;

      function TMultiPointD2<TPoints>.Max( const MPT: TMultiPointD2<TPoints>): TPointD2;

      var

        MPTA,

        MPTB: TPointD2;

      begin

        MPTA:=Max;

        MPTB:=MPT.Max;

        Result:=MPTA.Max( MPTB);

      end;

      class operator TMultiPointD2<TPoints>.Implicit( const MPT: TMultiPointD2<TPoints>): TPointsD2;

      begin

        Result:=MPT.Value;

      end;

      class operator TMultiPointD2<TPoints>.Implicit( const MPT: TPointsD2): TMultiPointD2<TPoints>;

      begin

        Result:=TMultiPointD2<TPoints>.Create(MPT);

      end;

      class operator TMultiPointD2<TPoints>.Implicit( const PT: TPointD2): TMultiPointD2<TPoints>;

      begin

        Result.Add( PT);

      end;

      class operator TMultiPointD2<TPoints>.LessThan( const PT: TPointD2; const MPT: TMultiPointD2<TPoints>): boolean;

      begin

        Result:=(MPT.Value.IndexOf( PT) > -1);

      end;

      class operator TMultiPointD2<TPoints>.LessThanOrEqual( const PT: TPointD2; const MPT: TMultiPointD2<TPoints>): boolean;

      begin

        Result:=(PT < MPT);

      end;

      class operator TMultiPointD2<TPoints>.GreaterThan( const MPT: TMultiPointD2<TPoints>; const PT: TPointD2): boolean;

      begin

        Result:=(PT < MPT);

      end;

      class operator TMultiPointD2<TPoints>.GreaterThanOrEqual( const MPT: TMultiPointD2<TPoints>; const PT: TPointD2): boolean;

      begin

        Result:=(PT <= MPT);

      end;

      function TMultiPointD2<TPoints>.Copy: TMultiPointD2<TPoints>;

      begin

        if(not Result.Equals( Self)) then

          begin

            Result.Clear;

            Result.Add(Self);

        end;

      end;

      procedure TMultiPointD2<TPoints>.Add( const PT: TPointD2);

      begin

        if(not PT.IsEmpty) then

          Value.Add( PT);

      end;

      procedure TMultiPointD2<TPoints>.Add( const AValue: TMultiPointD2<TPoints>);

      begin

        Value.AddRange( AValue.Value);

      end;

      class operator TMultiPointD2<TPoints>.Add( const MPT: TMultiPointD2<TPoints>; const PT: TPointD2): TMultiPointD2<TPoints>;

      begin

        if(not Result.Equals( MPT)) then      // X:=A+PT

          begin

            Result.Clear;

            Result.Add(MPT);

        end;

        Result.Add(PT);

      end;

      class operator TMultiPointD2<TPoints>.Add( const PT: TPointD2; const MPT: TMultiPointD2<TPoints>): TMultiPointD2<TPoints>;

      begin

        if(not Result.Equals( MPT)) then      // X:=PT+A;

          Result.Clear;

        Result.Value.Insert( 0, PT);

        if(not Result.Equals( MPT)) then      // not A:=PT+A;

          Result.Add(MPT);

      end;

      class operator TMultiPointD2<TPoints>.Add( const MPTA, MPTB: TMultiPointD2<TPoints>): TMultiPointD2<TPoints>;

      begin

        if(Result.Equals( MPTA)) then

          begin

            if(not Result.Equals( MPTB)) then // A:=A+B.

// Случай A:=A+A не обрабатывается! Это может привести к зацикливанию!

              Result.Add(MPTB)

          end

        else if(Result.Equals( MPTB)) then    // B:=A+B.

          Result.Value.InsertRange( 0, MPTA.Value)

        else

          begin                               // X:=A+B.

            Result.Clear;

            Result.Add(MPTA);

            Result.Add(MPTB);

        end;

      end;

      class operator TMultiPointD2<TPoints>.Subtract( const MPT: TMultiPointD2<TPoints>; const PT: TPointD2): TMultiPointD2<TPoints>;

      begin

        if(not Result.Equals( MPT)) then      // X:=A-PT.

          begin

            Result.Clear;

            Result.Add( MPT);

        end;

        Result.Delete(PT);                    // X:=A-PT или A:=A-PT.

      end;

      class operator TMultiPointD2<TPoints>.Subtract( const MPTA, MPTB: TMultiPointD2<TPoints>): TMultiPointD2<TPoints>;

      var

        PT: TPointD2;

        VP: TMultiPointD2<TPoints>;

      begin

        if(Result.Equals( MPTA)) then

          begin

            if(not Result.Equals( MPTB)) then // A:=A-B.

              begin

                for PT in MPTB.Value do

                  Result.Delete( PT);

            end;

          end

        else if(Result.Equals( MPTB)) then    // B:=A-B.

          begin

            VP:=MPTA.Copy();

            VP:=VP - MPTB;

            Result:=VP.Copy();

          end

        else

          begin                               // X:=A-B.

            Result:=MPTA.Copy();

            for PT in MPTB.Value do

              Result.Delete( PT);

        end;

      end;

      class operator TMultiPointD2<TPoints>.Multiply( const PT: TPointD2; const MPT: TMultiPointD2<TPoints>): TPointD2;

      begin

        Result.Empty;

        if((not (PT.IsEmpty or MPT.IsEmpty)) and (PT < MPT)) then

          Result:=PT;

      end;

      class operator TMultiPointD2<TPoints>.Multiply( const MPT: TMultiPointD2<TPoints>; const PT: TPointD2): TPointD2;

      begin

        Result:=PT*MPT;

      end;

      class operator TMultiPointD2<TPoints>.Multiply( const MPTA, MPTB: TMultiPointD2<TPoints>): TMultiPointD2<TPoints>;

      var

        PT: TPointD2;

      begin

        Result.Empty;

        if(not (MPTA.IsEmpty or MPTB.IsEmpty)) then

          begin

            for PT in MPTA.Value do

              begin

                if((not PT.IsEmpty) and (PT < MPTB)) then

                  Result.Add(PT);

            end;

        end;

      end;

      procedure TMultiPointD2<TPoints>.Delete( const AValue: TPointD2);

      begin

        Value.Remove(  AValue);

      end;

      procedure TMultiPointD2<TPoints>.Remove( const AValue: TPointD2);

      begin

        Value.Remove(  AValue);

      end;

// TPointsD2.TFreeTheValue.

      constructor TMultiPointD2<TPoints>.TFreeTheValue.Create( AObjectToFree: TObject);

      begin

        FObjectToFree:=AObjectToFree;

      end;

      destructor TMultiPointD2.TFreeTheValue.Destroy;

      begin

        FObjectToFree.Free;

        inherited Destroy;

      end;

Начинаем компилировать, и в ответ получаем следующие предупреждения об ошибках:

  1. Сообщение об ошибке E2037 Declaration of "" differs from previous declaration

Данная ошибка повторяется для реализации всех перегруженных операторов. Источник этой ошибки я так и не нашел. Хотя "лекарство" на нее нашлось: достаточно везде в списках параметров определений и реализаций перегруженных операторов убрать ключевое слово const как все данная ошибка исчезает. Но при этом мы получаем следующий список ошибок (см. Рисунок 4).

Решаем эту проблему прямыми подстановками кода перегруженных операторов. Например, код реализации

      class operator TMultiPointD2<TPoints>.LessThanOrEqual( PT: TPointD2; MPT: TMultiPointD2<TPoints>): boolean;

      begin

        Result:=(PT < MPT);

      end;

  1. Сообщение об ошибке E2015 Operator not applicable to this operand type.

может быть заменен следующим кодом:

  1. Пример исправления кода прегруженного оператора

      class operator TMultiPointD2<TPoints>.LessThanOrEqual( PT: TPointD2; MPT: TMultiPointD2<TPoints>): boolean;

      begin

        Result:=(MPT.Value.IndexOf( PT) > -1);

      end;

5. Пробуем снова компилировать. Получаем ошибку внутри кода тестовой процедуры:

  1. Сообщение об ошибки при компиляции кода тестовой процедуры.

procedure TForm4.Button7Click(Sender: TObject);

      var

        I: integer;

        MPTR: TMultiPointD2; // "мультиточка"

        MPTP: TMultiPointD2; // "полигон".

        PT: TPointD2;

        PTB: TPointD2;

        PTC: TPointD2;

      begin

        for I:= 1 to 25 do

          begin

            PT.X:=100.0*Random;

            PT.Y:=100.0*Random;

            MPTR.Add( PT);

        end;

        MPTP:=MPTR.Copy;

        Memo1.Lines.Add('До сортировки: мультиточка.');

        for PT in MPTR.Value do

          Memo1.Lines.Add(format('(X:%e, Y:%e)',[PT.X, PT.Y]));

        Memo1.Lines.Add('До сортировки: полигон.');

        for PT in MPTP.Value do

          Memo1.Lines.Add(format('(X:%e, Y:%e)',[PT.X, PT.Y]));

//================================.

// Сортируем "мультиточку".

        MPTR.Value.Sort;

//================================.

//Сортируем "полигон".

        MPTP.Value.Sort;

//================================.

        Memo1.Lines.Add(''После сортировки: мультиточка.');

        for PT in MPTR.Value do

          Memo1.Lines.Add(format('(X:%e, Y:%e)',[PT.X, PT.Y]));

        Memo1.Lines.Add('После сортировки: полигон.');

        for PT in MPTP.Value do

          Memo1.Lines.Add(format('(X:%e, Y:%e)',[PT.X, PT.Y]));

      end;

  1. Строка кода тестовой процедуры вызвавашая сообщение о синтаксической ошибке.

//........................................

//........................................

        MPTP:=MPTR.Copy;

//........................................

//........................................

Ее можно разрешить, добавив в базовый прикладной класс метод Copy:

  1. Код метода Copy.

      procedure TPointsD2.Copy( AValue: TPointsD2);

      var

        PT: TPointD2;

      begin

        AValue.Clear;

        for PT in Self do

          AValue.Add( PT);

      end;

Что приведет к следующему изменению кода тестовой процедуры:

  1. Исправленная реализация вызова метода Copy.

//........................................

//........................................

      MPTR.Value.Copy( MPTP.Value);

//........................................

//........................................

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

Остается только удивляться и надеяться, что кто-то из заинтересовавшихся талантливых читателей поймет в чем дело и объяснит автору этих строк причину его мытарств! Кто укажет путь истинный?!… Автор после всего этого хочет лишь повторить многозначительное восклицание Кисы Воробьянинова: "Да-а-а-а…уж…". Стоит ли игра свеч????!

Заключение

Эту статью я хотел полностью посвятить применению так называемых smart pointers при написании программ в среде Delphi 2009.  В целом, как мне кажется, это удалось.

Техника и приемы, которые носят название "smart pointers" конечно очень мощные и их применение позволило бы создавать гибкие и удобные для использования прикладными программистами библиотеки классов. Я надеюсь, что использование "smart pointers" не может быть ограничено использованием их в качестве некоторых временных хранилищ данных.

Однако, похоже, не бывает бочки меда без ложки дегтя. Это касается тех проблем, с которыми я столкнулся и которые описал в последнем разделе. Решат ли их разработчики Delphi - большой вопрос…

Литература


1. Cantu M. Delphi 2009. Handbook. pp. 171-176.

2. Cantu M. Delphi 2007. Handbook.

3. Kelly B. Smart pointers in Delphi. - http://blog.barrkel.com/2008/09/smart-pointers-in-delphi.html

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 28.09.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Delphi Professional Named User
Enterprise Connectors (1 Year term)
WinRAR 5.x Standard Licence - для частных лиц 1 лицензия
Microsoft Office 365 Бизнес. Подписка на 1 рабочее место на 1 год
AutoCAD LT 2022 Commercial New Single-user ELD Annual Subscription
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Все о PHP и даже больше
Утиль - лучший бесплатный софт для Windows
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100