Какие списки могут служить временными списками? - PullRequest
7 голосов
/ 10 ноября 2011

При работе со списками элементов, в которых списки служат просто временным контейнером - какие типы списков вы бы порекомендовали мне использовать?

Я

  • не хочу уничтожать список вручную
  • хотел бы использовать встроенный тип списка (без фреймворков, библиотеки, ...)
  • нужен универсальный шаблон

Что-то, что сделало бы это возможным без утечек:

function GetListWithItems: ISomeList;
begin
  Result := TSomeList.Create;
  // add items to list
end;

var
  Item: TSomeType;
begin
  for Item in GetListWithItems do
  begin
    // do something
  end;
end;

Какие варианты у меня есть?Речь идет о Delphi 2009, но ради знаний, пожалуйста, также укажите, есть ли что-то новое в этом отношении в 2010 +.

Ответы [ 7 ]

6 голосов
/ 10 ноября 2011

(каким-то уродливым) обходным путем для этого является создание интерфейса autodestroy вместе со списком.Он должен иметь ту же область видимости, чтобы после освобождения интерфейса ваш список также был уничтожен.

type
  IAutoDestroyObject = interface
  end;

  TAutoDestroyObject = class(TInterfacedObject, IAutoDestroyObject)
  strict private
    FValue: TObject;
  public
    constructor Create(obj: TObject);
    destructor  Destroy; override;
  end;

constructor TAutoDestroyObject.Create(obj: TObject);
begin
  inherited Create;
  FValue := obj;
end; 

destructor TAutoDestroyObject.Destroy;
begin
  FreeAndNil(FValue);
  inherited;
end;

function CreateAutoDestroyObject(obj: TObject): IAutoDestroyObject;
begin
  Result := TAutoDestroyObject.Create(obj);
end; 

FList := TObjectList.Create;
FListAutoDestroy := CreateAutoDestroyObject(FList);

Ваш пример использования также усложняется.

type
  TSomeListWrap = record
    List: TSomeList;
    AutoDestroy: IAutoDestroyObject;
  end;

function GetListWithItems: TSomeListWrap;
begin
  Result.List := TSomeList.Create;
  Result.AutoDestroy := CreateAutoDestroyObject(Result.List);
  // add items to list
end;

var
  Item: TSomeItem;
begin
  for Item in GetListWithItems.List do
  begin
    // do something
  end;
end;
5 голосов
/ 10 ноября 2011

Вдохновленный публикацией в блоге Барри Келли здесь вы можете реализовать умные указатели для своих целей, например:

unit Unit80;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Generics.Collections;

type
  TMyList =class( TList<Integer>)
  public
    destructor Destroy; override;
  end;

  TLifetimeWatcher = class(TInterfacedObject)
  private
    FWhenDone: TProc;
  public
    constructor Create(const AWhenDone: TProc);
    destructor Destroy; override;
  end;

  TSmartPointer<T: class> = record
  strict private
    FValue: T;
    FLifetime: IInterface;
  public
    constructor Create(const AValue: T); overload;
    class operator Implicit(const AValue: T): TSmartPointer<T>;
    property Value: T read FValue;
  end;

  TForm80 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    function getList : TSmartPointer<TMyList>;
    { Public declarations }
  end;


var
  Form80: TForm80;

implementation


{$R *.dfm}



{ TLifetimeWatcher }

constructor TLifetimeWatcher.Create(const AWhenDone: TProc);
begin
  FWhenDone := AWhenDone;
end;

destructor TLifetimeWatcher.Destroy;
begin
  if Assigned(FWhenDone) then
    FWhenDone;
  inherited;
end;

{ TSmartPointer<T> }

constructor TSmartPointer<T>.Create(const AValue: T);
begin
  FValue := AValue;
  FLifetime := TLifetimeWatcher.Create(procedure
  begin
    AValue.Free;
  end);
end;

class operator TSmartPointer<T>.Implicit(const AValue: T): TSmartPointer<T>;
begin
  Result := TSmartPointer<T>.Create(AValue);
end;

procedure TForm80.Button1Click(Sender: TObject);
 var i: Integer;
begin
  for I in getList.Value do
   Memo1.Lines.Add(IntToStr(i));

end;

{ TMyList }

destructor TMyList.Destroy;
begin
  ShowMessage('Kaputt');
  inherited;
end;

function TForm80.getList: TSmartPointer<TMyList>;
var
  x: TSmartPointer<TMyList>;
begin
  x := TMyList.Create;
  Result := x;
  with Result.Value do
  begin
    Add(1);
    Add(2);
    Add(3);
  end;
end;

end.

Посмотрите на getList и Button1click, чтобы увидеть его использование.

4 голосов
/ 10 ноября 2011

Для полной поддержки того, что вы ищете, потребуется поддержка двух вещей:

  • Сборщик мусора. Это единственное, что дает вам свободу ИСПОЛЬЗОВАТЬ что-то, не беспокоясь об освобождении этого. Я бы приветствовал изменение в Delphi, которое дало нам даже частичную поддержку для этого.
  • Возможность определения локальных инициализированных переменных. Опять же, я бы очень хотел увидеть что-то подобное.

Между тем, самое близкое, что вы можете получить, это использовать интерфейсы вместо сборки мусора (поскольку интерфейсы подсчитываются по ссылкам, как только они выходят из области видимости, они будут освобождены). Что касается инициализированных локальных переменных, вы можете использовать трюк, аналогичный описанному здесь: Объявление переменных уровня блока для ветвей в delphi

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

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils, Generics.Collections;

type

  ITemporaryLocalVar<T:constructor> = interface
    function GetL:T;
    property L:T read GetL;
  end;

  TTemporaryLocalVar<T:constructor> = class(TInterfacedObject, ITemporaryLocalVar<T>)
  public
    FL: T;

    constructor Create;
    destructor Destroy;override;

    function GetL:T;
  end;

  TTempUse = class
  public
    class function L<T:constructor>: ITemporaryLocalVar<T>;
  end;

{ TTemporaryLocalVar<T> }

constructor TTemporaryLocalVar<T>.Create;
begin
  FL := T.Create;
end;

destructor TTemporaryLocalVar<T>.Destroy;
begin
  TObject(FL).Free;
  inherited;
end;

function TTemporaryLocalVar<T>.GetL: T;
begin
  Result := FL;
end;

{ TTempUse }

class function TTempUse.L<T>: ITemporaryLocalVar<T>;
begin
  Result := TTemporaryLocalVar<T>.Create;
end;

var i:Integer;
begin
  try
    with TTempUse.L<TList<Integer>> do
    begin
      L.Add(1);
      L.Add(2);
      L.Add(3);
      for i in L do
        WriteLn(i);
    end;
    ReadLn;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
3 голосов
/ 10 ноября 2011

Стандартные классы списков, такие как TList, TObjectList, TInterfaceList и т. Д., Не реализуют автоматизированные жизненные циклы, поэтому вы должны освободить их вручную, когда закончите их использовать. Если вам нужен класс списка, доступный через интерфейс, вы должны реализовать его самостоятельно, например:

type
  IListIntf = interface
    ...
  end;

  TListImpl = class(TInterfacedObject, IListIntf)
  private
    FList: TList;
    ...
  public
    constructor Create; override;
    destructor Destroy; override;
    ...
  end;

constructor TListImpl.Create;
begin
  inherited;
  FList := TList.Create;
end;

destructor TListImpl.Destroy;
begin
  FList.Free;
  inherited;
end;

function GetListWithItems: IListIntf;
begin
  Result := TListImpl.Create;
  // add items to list
end;   
2 голосов
/ 10 ноября 2011

Другой вариант - реализовать универсальный адаптер IEnumerable (как один из способов удовлетворения требований компилятора for .. in) и рассчитывать на подсчет ссылок интерфейса.Я не знаю, работает ли следующее в Delphi 2009, похоже, что оно работает в Delphi XE:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes,
  Generics.Collections;

type
  // IEnumerator adapter for TEnumerator
  TInterfacedEnumerator<T> = class(TInterfacedObject, IEnumerator<T>)
  private
    FEnumerator: TEnumerator<T>;
  public
    constructor Create(AEnumerator: TEnumerator<T>);
    destructor Destroy; override;
    function IEnumerator<T>.GetCurrent = GetCurrent2;
    { IEnumerator }
    function GetCurrent: TObject;
    function MoveNext: Boolean;
    procedure Reset;
    { IEnumerator<T> }
    function GetCurrent2: T;
  end;

  // procedure used to fill the list    
  TListInitProc<T> = reference to procedure(List: TList<T>);

  // IEnumerable adapter for TEnumerable
  TInterfacedEnumerable<T> = class(TInterfacedObject, IEnumerable<T>)
  private
    FEnumerable: TEnumerable<T>;
  public
    constructor Create(AEnumerable: TEnumerable<T>);
    destructor Destroy; override;
    class function Construct(InitProc: TListInitProc<T>): IEnumerable<T>;
    function IEnumerable<T>.GetEnumerator = GetEnumerator2;
    { IEnumerable }
    function GetEnumerator: IEnumerator; overload;
    { IEnumerable<T> }
    function GetEnumerator2: IEnumerator<T>; overload;
  end;

{ TInterfacedEnumerator<T> }

constructor TInterfacedEnumerator<T>.Create(AEnumerator: TEnumerator<T>);
begin
  inherited Create;
  FEnumerator := AEnumerator;
end;

destructor TInterfacedEnumerator<T>.Destroy;
begin
  FEnumerator.Free;
  inherited Destroy;
end;

function TInterfacedEnumerator<T>.GetCurrent: TObject;
begin
  Result := TObject(GetCurrent2);
end;

function TInterfacedEnumerator<T>.GetCurrent2: T;
begin
  Result := FEnumerator.Current;
end;

function TInterfacedEnumerator<T>.MoveNext: Boolean;
begin
  Result := FEnumerator.MoveNext;
end;

procedure TInterfacedEnumerator<T>.Reset;
begin
  // ?
end;

{ TInterfacedEnumerable<T> }

class function TInterfacedEnumerable<T>.Construct(InitProc: TListInitProc<T>): IEnumerable<T>;
var
  List: TList<T>;
begin
  List := TList<T>.Create;
  try
    if Assigned(InitProc) then
      InitProc(List);
    Result := Create(List);
  except
    List.Free;
    raise;
  end;
end;

constructor TInterfacedEnumerable<T>.Create(AEnumerable: TEnumerable<T>);
begin
  inherited Create;
  FEnumerable := AEnumerable;
end;

destructor TInterfacedEnumerable<T>.Destroy;
begin
  FEnumerable.Free;
  inherited Destroy;
end;

function TInterfacedEnumerable<T>.GetEnumerator: IEnumerator;
begin
  Result := GetEnumerator2;
end;

function TInterfacedEnumerable<T>.GetEnumerator2: IEnumerator<T>;
begin
  Result := TInterfacedEnumerator<T>.Create(FEnumerable.GetEnumerator);
end;

type
  TSomeType = record
    X, Y: Integer;
  end;

function GetList(InitProc: TListInitProc<TSomeType>): IEnumerable<TSomeType>;
begin
  Result := TInterfacedEnumerable<TSomeType>.Construct(InitProc);
end;

procedure MyInitList(List: TList<TSomeType>);
var
  NewItem: TSomeType;
  I: Integer;
begin
  for I := 0 to 9 do
  begin
    NewItem.X := I;
    NewItem.Y := 9 - I;
    List.Add(NewItem);
  end;
end;

procedure Main;
var
  Item: TSomeType;
begin
  for Item in GetList(MyInitList) do // you could also use an anonymous procedure here
    Writeln(Format('X = %d, Y = %d', [Item.X, Item.Y]));
  Readln;
end;

begin
  try
    ReportMemoryLeaksOnShutdown := True;
    Main;
  except
    on E: Exception do
    begin
      ExitCode := 1;
      Writeln(Format('[%s] %s', [E.ClassName, E.Message]));
    end;
  end;
end.
1 голос
/ 10 ноября 2011

В библиотеке кодов джедаев существует функция Guard , которая уже реализует то, что делает код Габра.

1 голос
/ 10 ноября 2011

Нет, не «из коробки» в Delphi.Я знаю, что вам не нужна библиотека, но вас может заинтересовать принцип TDynArray .

...