Delphi:在类中创建TStringList时内存泄漏

时间:2014-05-28 15:56:02

标签: class delphi memory-leaks tstringlist

我有这段代码

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 个答案:

答案 0 :(得分:11)

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

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

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

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

答案 1 :(得分: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)。但是,通过公开展示它,您将失去许多好处。具体而言,您将面临Law of Demeter违规行为。我不是说永远不要这样做。但请注意,如果大量客户端代码直接访问ConnString,您可以在线创建维护问题。

  2. 声明FConnString: TStringList;违反了Program to an interface, not an implementation的原则。 TStringListTStrings的特定实现,此声明禁止使用TStrings的其他子类。这是与#1相结合的更多问题:如果在几年内您发现并希望切换到TStrings的{​​{1}}客户端代码的不同/更好的子类实现,那么绑定到TStringList的客户端代码现在创建了很多更多的工作和风险。基本上,首选的方法可以概括为:

    • 将变量声明为abtract基类型。
    • 将实例创建为专门选择的实现子类。
    • 让多态性确保在重写方法中正确应用子类行为。
  3. 这也是一般指导原则。如果您特别需要访问在层次结构的TStringList级别添加的属性/方法,则必须绑定到该类。但如果你不需要它,就不要这样做。

答案 2 :(得分:1)

您忘记使用Destroy()说明符声明override,因此在销毁对象时实际上并未调用TSql.Destroy

destructor Destroy; override;

答案 3 :(得分:0)

未正确创建对象。必须是:

Sql:=TSql.Create;

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