Class Delphi:在类中创建TStringList时内存泄漏
我有这段代码Class Delphi:在类中创建TStringList时内存泄漏,class,delphi,memory-leaks,tstringlist,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}
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时会在按下按钮后产生内存泄漏
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
..................................
..................................
编辑实际问题
问题中的原始代码如下:
procedure TForm1.Button1Click(Sender: TObject);
begin
Sql.Create;
Sql.ConnString.Add('something');
showmessage(Sql.ConnString.Text);
Sql.Destroy;
end;
问题行是Sql.Create
应该是Sql:=TSql.Create代码>。这导致内存泄漏的原因如下:
procedure TForm1.Button1Click(Sender: TObject);
begin
Sql.Create;
Sql.ConnString.Add('something');
showmessage(Sql.ConnString.Text);
Sql.Destroy;
end;
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;
编辑
一些一般提示:
我为您使用组合(使FConnString
成为成员而不是继承TStringList
)而喝彩。然而,如果将其公开,您将失去许多好处。具体来说,您将面临违规行为。我不是说永远不要这样做。但是请注意,如果大量客户端代码直接访问ConnString
,则可能会造成维护问题
声明FConnString:TStringList
违反了安全性原则TStringList
是TStrings
的一个特定实现,此声明阻止使用TStrings
的其他子类。这更像是一个与#1结合的问题:如果在几年后,您发现并希望切换到与TStringList
绑定的TStringList
客户端代码的不同/更好的子类实现,那么现在会产生更多的工作和风险。基本上,首选方案可归纳为:
- 将变量声明为abtract基类型
- 创建实例作为特定选择的实现子类
- 让多态性确保在重写的方法中正确应用子类行为
这也是一个一般性的指导方针。如果您特别需要访问在层次结构的TStringList
级别添加的属性/方法,那么您必须绑定到该类。但是如果您不需要它,就不要这样做。您忘记了用重写说明符声明Destroy()
,因此在销毁对象时实际上不会调用TSql.Destroy
destructor Destroy; override;
对象创建不正确。它必须是:
Sql:=TSql.Create;
内存泄漏就从这里开始了。我看到了两件事。关于析构函数缺少“覆盖”的其他评论和回答已经涵盖了第一个问题
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;
第二个问题是财产申报本身。通常,您不应该声明引用“write”子句中的对象字段的属性。原因是,分配给该属性将“泄漏”该字段中的现有实例。对属性声明的“write”子句使用一种方法:
property ConnString: TStringList read FConnString write SetConnString;
...
procedure TSql.SetConnString(Value: TStringList);
begin
FConnString.Assign(Value);
end;
还要注意,此方法也不会覆盖FConnString字段。它只是将值TStringList的“值”或“内容”复制到FConnString实例中。通过这种方式,TSql实例在该字段的生存期内完全处于控制状态。代码负责分配该属性以控制值TStringlist的生存期。让我展开。如果您能够在没有错误的情况下调用Sql.Create,那么您已经有了一个有效的Tsql实例。当您对其调用Create时,它会创建另一个FConnString。看,去掉那一行,你就没事了。当然应该是Sql:=TSql.Create代码>?耶。。。我的错。你是赖特!它起作用了!无论如何,我的真实代码仍然有问题。我会更深入地寻找。在本文中,我的原始代码是Sql:=TSql.Create,但仍然存在问题。我将问题回滚。瞄准一个移动的目标是没有乐趣的。这个全局变量让我感觉析构函数已经是虚拟的,在TObject
中。你需要声明is asoverride
。修正2无法修复漏洞,TObject。Destroy不起任何作用。“1也是如此。”SertacAkyuz说得对,它们是额外的问题。我已经更新了对实际内存泄漏的解释。@Craig-谢谢。我确信这不是问题所在,因为问题中会提到AV。问题是,这个问题是假的。描述得很好的答案。除了这个错误,我从你详细的回答中学到了更多的东西。谢谢@CraigYoung!没有人打电话?他正在调用Tsql.Destroy。虽然他应该重写Destroy
,但在这种情况下,代码直接调用Destroy
,因此此问题并没有解决真正的问题。不,这会导致AV。@SertacAkyuz我对您的评论投了赞成票,然后意识到错误实际上是导致内存泄漏的原因(见我的最新答案)。(唉,我不能取消对评论的投票。)@Craig-我认为我的评论没有任何问题。提问者肯定没有AV,否则他会在他的问题中有AV。