我有这段代码
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会造成内存泄漏?
.................................. .................................. .................................. ..................................
答案 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
。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
)。但是,通过公开展示它,您将失去许多好处。具体而言,您将面临Law of Demeter违规行为。我不是说永远不要这样做。但请注意,如果大量客户端代码直接访问ConnString
,您可以在线创建维护问题。
声明FConnString: TStringList;
违反了Program to an interface, not an implementation的原则。 TStringList
是TStrings
的特定实现,此声明禁止使用TStrings
的其他子类。这是与#1相结合的更多问题:如果在几年内您发现并希望切换到TStrings
的{{1}}客户端代码的不同/更好的子类实现,那么绑定到TStringList
的客户端代码现在创建了很多更多的工作和风险。基本上,首选的方法可以概括为:
这也是一般指导原则。如果您特别需要访问在层次结构的TStringList
级别添加的属性/方法,则必须绑定到该类。但如果你不需要它,就不要这样做。
答案 2 :(得分:1)
您忘记使用Destroy()
说明符声明override
,因此在销毁对象时实际上并未调用TSql.Destroy
。
destructor Destroy; override;
答案 3 :(得分:0)
未正确创建对象。必须是:
Sql:=TSql.Create;
从这里开始出现内存泄漏。