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

REA*_*OFO -1 delphi memory-leaks class 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;
Run Code Online (Sandbox Code Playgroud)

在按下按钮后,为什么在创建FConnString时会产生内存泄漏?

.................................. ................ .................. ................................ .. ..................................

All*_*uer 11

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

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

property ConnString: TStringList read FConnString write SetConnString;
...
procedure TSql.SetConnString(Value: TStringList);
begin
  FConnString.Assign(Value);
end;
Run Code Online (Sandbox Code Playgroud)

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


Dis*_*ned 9

编辑实际问题

问题中的原始代码如下:

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

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

  • Sql.Create;引用中调用.
  • 这会调用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;
Run Code Online (Sandbox Code Playgroud)

编辑

一些一般提示:

  1. 我赞赏你使用组合(制作FConnString成员而不是继承TStringList).但是,通过公开展示它,您将失去许多好处.具体而言,您将接触到Demeter违规.我不是说永远不要这样做.但请注意,如果大量客户端代码ConnString直接访问,您可以在线下创建维护问题.

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

    • 将变量声明为abtract基类型.
    • 将实例创建为特别选择的实现子类.
    • 让多态性确保在重写方法中正确应用子类行为.

这是一般指导原则.如果您特别需要访问TStringList在层次结构级别添加的属性/方法,则必须绑定到该类.但如果你不需要它,不要这样做.

  • 析构函数已经是虚拟的,在`TObject`中.你需要声明为`override`而不是. (2认同)