这段代码是否可以防止内存泄漏?
s := TStringList.Create; // create first object
try
// Here line comes that seems to be dangerous
s := GetSomeSettings; // Overrides reference to first object by second one
finally
s.free; // Destroying only second object, leave first object to live somewhere in memory
end;
function GetSomeSettings : TStringList;
var
rawString : string;
settings : TStringList;
begin
// Singleton pattern implementation
// Trying to find already existing settings in class variable
settings := TSettingsClass.fSettings;
// If there is no already defined settings then get them
if not Assigned(settings) then
begin
GetSettingsInDB(rawString);
TSettingsClass.fSettings := ParseSettingsString(rawString);
settings := TSettingsClass.fSettings;
end;
Result := settings;
end;
我想知道s := GetSomeSettings;
可能有害并忽略第一个对象,将其保留在记忆中?
答案 0 :(得分:18)
是的,第1行创建的StringList被泄露。
Essentialy,你在做:
s := TStringList.Create;
s := AnotherStringList;
AnotherStringList.Free;
至于GetSomeSettings
例程:
通常,将新创建的实例作为函数结果返回是不明智或鼓励的,因为您将所有权和销毁的责任转移到调用代码。除非你有一个机制/框架来处理它,你的TSettingsClass
似乎就是这种情况,但在这一小段代码中没有足够的证据。
然而,两段代码的组合显示另一个问题:在s.Free
之后,TSettingsClass.fSettings
被销毁但不是零。因此,第二次调用GetSomeSettings
时,它会返回一个悬空指针。
答案 1 :(得分:7)
1)你不应该问什么时候可以在两分钟内办理登机手续!
program {$AppType Console};
uses Classes, SysUtils;
type TCheckedSL = class(TStringList)
public
procedure BeforeDestruction; override;
procedure AfterConstruction; override;
end;
procedure TCheckedSL.BeforeDestruction;
begin
inherited;
WriteLn('List ',IntToHex(Self,8), ' going to be safely destroyed.');
end;
procedure TCheckedSL.AfterConstruction;
begin
WriteLn('List ',IntToHex(Self,8), ' was created - check whether it is has matched destruction.');
inherited;
end;
procedure DoTest; var s: TStrings;
function GetSomeSettings: TStrings;
begin Result := TCheckedSL.Create end;
begin
Writeln('Entered DoTest procedure');
s := TCheckedSL.Create; // create first object
try
// Here line comes that seems to be dangerous
s := GetSomeSettings; // Overrides reference to first object by second one
finally
s.free; // Destroying only second object, leave first object
end;
Writeln('Leaving DoTest procedure');
end;
BEGIN
DoTest;
Writeln;
Writeln('Check output and press Enter when done');
ReadLn;
END.
2)在少数利基案件中,这仍然是安全的。
S
可能是某个单位的“全局属性”,有一个可以释放旧列表的setter。S
中的TStringList
缺少任何接口,但是一些库(例如http://jcl.sf.net)确实提供了基于接口的字符串列表,具有更丰富的API(iJclStringList
类型和相关的)。S
声明为记录 - 一个已重新定义Extended Record
的所谓class operator Implicit
,以便类型s{record} := TStringList.Create
在分配之前释放前一个实例新的一个。这很危险,因为它非常脆弱且容易被滥用,并在其他地方销毁列表,在S
记录中留下悬空指针。TStringList
,而是一些子类,覆盖构造函数或AfterConstruction
在一些列表中注册自己,这在某个地方是一次性的。围绕大量工作负载的Mark/Sweep
堆管理。 VCL TComponent可能被视为遵循以下模式:form拥有其组件并在需要时释放它们。这就是你 - 以简化形式 - 试图用TSettingsClass.fSettings
包含(任何引用是1大小的容器)。但是这些框架确实需要一个环回:当对象被释放时,它也应该从所有容器中移除它自己,引用它。
procedure TCheckedSL.BeforeDestruction;
begin
if Self = TSettingsClass.fSettings then TSettingsClass.fSettings := nil;
inherited;
end;
class procedure TSettingsClass.SetFSettings(Value);
var fSet2: TObject;
begin
if fSettings <> nil then begin
fSet2 := fSettings;
f_fSettings := nil; // breaking the loop-chain
fSet2.Destroy;
end;
f_fSettings := Value;
end;
class destructor TSettingsClass.Destroy;
begin
fSettings := nil;
end;
然而 - 然后 - 显然需要保持设计对称 - 注册也应该作为课程的一部分来完成。负责注销的人通常也是负责注册的人,除非有理由扭曲设计。
procedure TCheckedSL.AfterConstruction;
begin
inherited;
TSettingsClass.fSettings := Self;
end;
...
if not Assigned(settings) then
begin
GetSettingsInDB(rawString);
TCheckedSL.Create.Text := ParseSettingsString(rawString);
settings := TSettingsClass.fSettings;
Assert( Assigned(settings), 'wrong class used for DB settings' );
end;
Result := settings;