0

我有这段代码

TSql = class
  private
    FConnString: TStringList;
  public
    property ConnString: TStringList read FConnString write FConnString;
    constructor Create;
    destructor Destroy;
  end;

var 
  Sql: TSql;

...

implementation

{$R *.dfm}

constructor TSql.Create;
begin
  //inherited Create;
  FConnString:=TStringList.Create;
end;

destructor TSql.Destroy;
begin
  FConnString.Free;
  //inherited Destroy;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Sql.Create;
  Sql.ConnString.Add('something');
  showmessage(Sql.ConnString.Text);
  Sql.Destroy;
end;

为什么在创建 FConnString 时会在按下按钮后造成内存泄漏?

..................................................... ..................................................... ………………………………………………………………………………………………………………………………

4

4 回答 4

11

我看到了两件事。关于析构函数缺乏“覆盖”的其他评论和答案已经涵盖了其中的第一个。

第二个问题是属性声明本身。通常,您永远不应该在“write”子句中声明引用对象字段的属性。原因是分配给该属性将“泄漏”该字段中的现有实例。使用属性声明的“write”子句的方法:

property ConnString: TStringList read FConnString write SetConnString;
...
procedure TSql.SetConnString(Value: TStringList);
begin
  FConnString.Assign(Value);
end;

另请注意,此方法也不会覆盖 FConnString 字段。它只是将 Value TStringList 的“值”或“内容”复制到 FConnString 实例中。以这种方式,TSql 实例完全控制了该字段的生命周期。分配该属性的代码负责控制 Value TStringlist 的生命周期。

于 2014-05-28T16:47:43.100 回答
9

编辑实际问题

问题中的原始代码如下:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Sql.Create;
  Sql.ConnString.Add('something');
  showmessage(Sql.ConnString.Text);
  Sql.Destroy;
end;

问题线Sql.Create;应该是Sql := TSql.Create;. 这导致内存泄漏的原因如下:

  • Sql.Create;nil引用中调用。
  • 这会调用TStringList.Create;并尝试将结果分配给FConnString.
  • 因为 Sql 是一个 nil 引用,所以这会触发访问冲突。
  • 问题是无法销毁TStringList创建的实例。

原始答案中的其他问题

你的析构函数是虚拟的,你没有覆盖。
你没有调用你继承的析构函数。

TSql = class
  private
    FConnString: TStringList;
  public
    property ConnString: TStringList read FConnString write FConnString;
    constructor Create;
    destructor Destroy; override; //Correction #1
  end;

destructor TSql.Destroy;
begin
  FConnString.Free;
  inherited Destroy; //Correction #2
end;

编辑

一些一般提示:

  1. 我为您使用组合(制作FConnString成员而不是继承自TStringList)而鼓掌。但是,通过公开暴露它,您将失去许多好处。具体来说,您将面临违反德米特法则的风险。我不是说永远不要这样做。但请注意,如果直接访问大量客户端代码,您可能会在后续产生维护问题ConnString

  2. 对接口声明FConnString: TStringList;违反Program 原则,而不是对实现声明。TStringList是 的特定实现,TStrings并且此声明阻止使用TStrings. 与#1 相结合,这更像是一个问题:如果在几年后您发现并想要切换到TStrings绑定到TStringList现在的客户端代码的不同/更好的子类实现,则会产生更多的工作和风险。基本上首选的方法可以总结为:

    • 将变量声明为抽象基类型。
    • 将实例创建为专门选择的实现子类。
    • 让多态性确保子类行为在被覆盖的方法中正确应用。

这又是一个一般准则。如果您特别需要访问在TStringList层次结构级别添加的属性/方法,那么您必须绑定到该类。但是,如果您不需要它,请不要这样做。

于 2014-05-28T16:11:58.473 回答
1

您忘记Destroy()使用说明override符进行声明,因此TSql.Destroy当对象被销毁时实际上并没有被调用。

destructor Destroy; override;
于 2014-05-28T16:12:07.933 回答
0

对象未正确创建。肯定是:

Sql:=TSql.Create;

从这里开始出现内存泄漏。

于 2014-05-28T16:36:58.447 回答