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)在少数利基情况下,这仍然是安全的。
- 在 FPC ( http://FreePascal.org )中
S
可能是某个单元的“全局属性”,有一个可以释放旧列表的设置器。
- 在 Delphi Classic
S
中,可以是某种接口类型,由创建的对象支持。当然,标准TStringList
缺少任何接口,但一些库(例如http://jcl.sf.net)确实提供了基于接口的字符串列表,具有更丰富的 API(iJclStringList
类型和相关)。
- 在 Delphi/LLVM 中,所有对象都进行了引用计数,就像没有 GUID 的接口一样。所以那个代码在那里是安全的。
- 您可以声明
S
为记录 - 所谓Extended Record
的重新定义class operator Implicit
,以便类型转换s{record} := TStringList.Create
在分配新实例之前释放前一个实例。不过这很危险,因为它非常脆弱且容易被滥用,并且会在其他地方破坏列表,从而在S
记录中留下一个悬空的指针。
- 您的对象可能不是那个 vanilla
TStringList
,而是一些子类,覆盖构造函数或AfterConstruction
在某个列表中注册自己,这将在某个地方一次性完成。Mark/Sweep
围绕大量工作负载进行堆管理。VCL TComponent 可以被视为遵循这种模式:表单拥有它的组件并在需要时释放它们。这就是你 - 以简化的形式 - 试图用容器做的事情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;