【问题标题】:Delphi XE7 smart pointersDelphi XE7 智能指针
【发布时间】:2015-03-31 20:43:12
【问题描述】:

我是 Delphi 的新手,具有 C++ 背景,并试图弄清楚如何实现智能指针。我遇到了以下帖子,我试图将其用作我自己的起点:Delphi - smart pointers and generics TList

但是我无法使用 Delphi XE7 编译之前的代码(编译器错误在代码中显示为 cmets)。另外,如果有人真正解释了代码的逻辑,我将不胜感激(最初我想将该类用作实用程序类的一个下降,但现在我想了解实际发生的情况)。我隐约明白,因为智能指针实现是从 TInterfacedObject 继承的,所以它是引用计数的,但除此之外的任何东西对我来说都没有意义:)

unit SmartPointer;

interface

uses
  SysUtils, System.Generics.Collections;

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

  // complains ISmartPointer<T> expecting an interface type
  TSmartPointer<T: class, constructor> = class(TInterfacedObject,ISmartPointer<T>)
  private
    FValue: T;
  public
    constructor Create; overload;
    constructor Create(AValue: T); overload;
    destructor Destroy; override;
    function Invoke: T;
  end;

implementation

{ TSmartPointer<T> }

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

// complains: overload procedure TSmartPointer.Create must be marked with the overload directive
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
  FValue.Free;
  inherited;
end;

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

end.

尝试将先前的智能指针与以下测试代码一起使用,导致编译器错误……我错过了什么?

program TestSmartPointer;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, SmartPointer;

type
TPerson = class
  private
    _name : string;
    _age : integer;
  public

    property Name: string read _name write _name;
    property Age: integer read _age write _age;
  end;

var
  pperson : TSmartPointer<TPerson>;

begin
  try
    { TODO -oUser -cConsole Main : Insert code here }
    pperson := TSmartPointer<TPerson>.Create();
    // error on next line: undeclared Identifier: Name
    pperson.Name := 'John Doe';
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

【问题讨论】:

  • 我的建议是不要使用智能指针。它们是不适合该语言的成语。
  • 关于顺序,在 C++ RAII 中,您知道当对象超出范围时,所有资源都会被清理,以相反的获取顺序。您不知道这些 Delphi“智能”指针会发生什么顺序。您所知道的是,它发生在过程返回时。范围不能小于过程。而且你无法控制顺序。如果这是个好主意,那么每个人都会这样做。
  • 智能指针是死胡同。使用 try/finally 处理对象的生命周期是简单且可预测的。如果您犯了错误,FastMM 可以帮助您立即找到泄漏点。 RTL 有很多问题,但是手动处理对象造成的内存泄漏并不是这里的大问题。 (移动 ARC 模型是另一种球类游戏,在当前状态下,存在大量错误和混乱)。
  • @DavidHeffernan 您可以拥有比过程更小的可预测顺序和范围。您所要做的就是将智能指针设为 nil,它会在此时触发析构函数(当然,如果您对智能指针实例的引用不超过一个)。
  • 我认为在 Delphi 中使用智能指针没有任何问题。它们没有什么不适合 Delphi 语言的。如果引用计数对象实例合适,那么智能指针也合适。

标签: delphi smart-pointers delphi-xe7


【解决方案1】:

你必须将你的引用变量声明为ISmartPointer&lt;TPerson&gt;:

var
  pperson : ISmartPointer<TPerson>;

以下代码也可以编译,但在这种情况下它不会自动释放内存,因为当您将引用计数的对象实例存储到对象引用中时,您会弄乱它的引用计数机制。根据代码的不同,它可能会导致内存泄漏或底层对象实例的过早破坏。

var
  pperson : TSmartPointer<TPerson>;

begin
  pperson := TSmartPointer<TPerson>.Create();
  pperson.Invoke.Name := 'John Doe';

最后,下面的代码说明了正确的智能指针用法:

var
  pperson : ISmartPointer<TPerson>;   // note pperson is ISmartPointer<TPerson>

begin
  pperson := TSmartPointer<TPerson>.Create();
  pperson.Name := 'John Doe';

一些界面基础知识

接口定义了一个契约——实现该接口的类必须具有的功能,而不提供特定的实现。 IFoo 接口声明意味着当您引用 IFoo 时,您可以在该引用上调用 Foo 方法,但仅此而已。

IFoo = interface
  procedure Foo;
end;

当一个类实现一个接口时,它必须实现该接口的所有方法。来自IFoo 的方法Foo 将映射到来自TFoo 的方法FooTOtherFoo。具体接口的实现在不同的类中可以不同。

TFoo = class(TInterfacedObject, IFoo)
public
  procedure Foo;
  procedure Bar;
end;

TOtherFoo = class(TInterfacedObject, IFoo)
public
  procedure Foo;
end;

procedure TFoo.Bar;
begin
  writeln('Bar');
end;

procedure TFoo.Foo;
begin
  writeln('Foo');
end;

procedure TOtherFoo.Foo;
begin
  writeln('Other Foo');
end;

var
  foo: IFoo;
  f: TFoo;

  foo := TFoo.Create;
  foo.Foo; // Output -> Foo

  // Compiler error -> foo is interface reference and only knows Foo from TFoo
  foo.Bar;

  foo := TOtherFoo.Create;
  foo.Foo; // Output -> Other Foo

  // Mixing object reference with reference counted object instance -> memory leaks
  f := TFoo.Create;
  foo.Foo; // output -> Foo
  foo.Bar; // f is TFoo object reference, and it knows everything from TFoo

智能指针的实际工作原理

ISmartPointer&lt;T&gt; 被声明为匿名函数。

ISmartPointer<T> = reference to function: T;

上面的声明相当于带有Invoke函数的接口

ISmartPointer<T> = interface
  function Invoke: T;
end;

两者之间的区别(我们在这里感兴趣的那个)是使用匿名函数/方法,您不必显式调用Invoke;编译器会为你做这些。

由于ISmartPointer&lt;T&gt;是一个匿名函数,实际上是TSmartPointer&lt;T&gt;类声明中的一个接口,所以Invoke方法会映射到ISmartPointer&lt;T&gt;

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

var
  pperson : ISmartPointer<TPerson>;

因此,当您在幕后编写pperson.Name 时,它会转换为pperson.Invoke 函数调用,该函数调用会从FValue 返回一个TPerson 实例,并且TPerson 具有编译器可以识别的Name 属性。

由于TSmartPointer&lt;T&gt;是一个引用计数类,当你使用ISmartPointer&lt;T&gt;引用时,底层的TSmartPointer&lt;T&gt;对象实例,连同它包含在FValue中的T实例将在@时自动释放987654356@ 引用超出范围,或者您在代码中将其设置为nil

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-29
    • 1970-01-01
    • 2016-10-09
    • 1970-01-01
    • 2021-11-28
    • 2020-07-10
    • 2017-07-04
    • 1970-01-01
    相关资源
    最近更新 更多