Delphi - 智能指针和泛型 TList

Delphi - smart pointers and generics TList

我有一个智能指针的实现,我试图在通用 TList 上实现它。

program Project2;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Generics.Collections;


type
  ISmartPointer<T> = reference to function: T;

  TSmartPointer<T: class, constructor> = class(TInterfacedObject, ISmartPointer<T>)
  private
    FValue: T;
    FName: string;
  public
    constructor Create; overload;
    constructor Create(AValue: T); overload;
    destructor Destroy; override;
    function Invoke: T;

    property Name: string read FName write FName;
  end;

  TTest = class
    FString: String;
  public
   property MyStrign: String read FString write FString;
  end;

{ TSmartPointer<T> }

constructor TSmartPointer<T>.Create;
begin
  inherited;

  FValue := T.Create;
end;

constructor TSmartPointer<T>.Create(AValue: T);
begin
  inherited Create;
  if AValue = nil then
    FValue := T.Create
  else
    FValue := AValue;
end;

destructor TSmartPointer<T>.Destroy;
begin
  if Assigned(FValue) then
    FValue.Free;

  inherited;
end;

function TSmartPointer<T>.Invoke: T;
begin
  Result := FValue;
end;

function TestSMP():ISmartPointer<TList<TTest>>;
var lTTest: ISmartPointer<TTest>;
    i: Integer;
begin
 Result := TSmartPointer<TList<TTest>>.Create();

 for I := 0 to 5 do
  begin
    lTTest := TSmartPointer<TTest>.Create();
    lTTest.FString := IntToStr(i);

    Result().Add(lTTest);
  end;
end;

var Testlist:ISmartPointer<TList<TTest>>;
    i: Integer;

begin
  try
   Testlist := TestSMP();

   for I := 0 to 5 do
    Writeln(Testlist[i].FString);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
 Writeln('finished');
 Readln;
end.

问题是我无法访问列表中的元素,而且我不知道问题出在哪里。

function TestSMP(): ISmartPointer<TList<TTest>>;
var 
   lTTest: ISmartPointer<TTest>;
   i: Integer;
begin
   Result := TSmartPointer<TList<TTest>>.Create();

   for I := 0 to 5 do
   begin
      lTTest := TSmartPointer<TTest>.Create();
      lTTest.FString := IntToStr(i);

      Result().Add(lTTest);
   end;
end;

lTTest 接口变量是唯一使 TTest 实例保持活动状态的东西。每次循环,当您分配给 lTTest 时,先前的 TTest 实例将被销毁。当函数退出时,最终的 TTest 实例,即包含 '5' 的实例被销毁。您精心创建的所有实例现在都已死亡。

您可以通过在 TSmartPointer<T>.Destroy 中放置一个断点并查看调用堆栈来观察这种情况。这样做的后果之一是您的代码实际上是在 TTest 实例被销毁后引用它们。碰巧,你和我都没有观察到运行时错误,尽管这样做显然是错误的。

这里的关键点是,一旦开始使用智能指针管理生命周期,就必须完全这样做。一分钱,一英镑。这几乎促使您更换

ISmartPointer<TList<TTest>>

ISmartPointer<TList<ISmartPointer<TTest>>>

那是因为您已经开始通过智能点包装来管理 TTest 实例生命周期。一旦开始这样做,就必须始终如一地这样做。

考虑你程序的这个变体:

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.Generics.Collections;

type
  ISmartPointer<T> = reference to function: T;

  TSmartPointer<T: class, constructor> = class(TInterfacedObject,
    ISmartPointer<T>)
  private
    FValue: T;
  public
    constructor Create;
    destructor Destroy; override;
    function Invoke: T;
  end;

  TTest = class
    FString: string;
  end;

constructor TSmartPointer<T>.Create;
begin
  inherited;
  FValue := T.Create;
end;

destructor TSmartPointer<T>.Destroy;
begin
  FValue.Free;
  inherited;
end;

function TSmartPointer<T>.Invoke: T;
begin
  Result := FValue;
end;

function TestSMP(): ISmartPointer<TList<ISmartPointer<TTest>>>;
var
  lTTest: ISmartPointer<TTest>;
  i: Integer;
begin
  Result := TSmartPointer<TList<ISmartPointer<TTest>>>.Create();

  for i := 0 to 5 do
  begin
    lTTest := TSmartPointer<TTest>.Create();
    lTTest.FString := IntToStr(i);
    Result().Add(lTTest);
  end;
end;

var
  i: Integer;
  Testlist: ISmartPointer<TList<ISmartPointer<TTest>>>;

begin
  Testlist := TestSMP();
  for i := 0 to 5 do
    Writeln(Testlist[i]().FString);
  Writeln('finished');
  Readln;
end.

输出

0
1
2
3
4
5
finished

我觉得我不太喜欢ISmartPointer<TList<ISmartPointer<TTest>>>这个想法。老实说,我从来没有被智能指针在 Delphi.

中的有效性所说服。