Как безопасно создавать и удалять несколько объектов в Delphi

Как вы можете безопасно создавать и освобождать несколько объектов?

В принципе, такого рода вещи:

  newOrderSource := TWebNewOrderSource.Create();
  twData := TTWData.Create();
  webData := TWebData.Create();

  try
    //do stuff
  finally
    newOrderSource.Free();
    twData.Free();
    webData.Free();
  end;

В этом случае вторая и третья команды create небезопасны, поскольку они работают с базой данных. Должен ли я просто поместить все создатели в блок try и проверить, назначены ли они до того, как я позвоню бесплатно?

Ответ 1

Вы можете сделать это с помощью одного блока try, если назначить nil первым, как,

newOrderSource := nil;
twData := nil;
webData := nil;
try
  newOrderSource := TWebNewOrderSource.Create();    
  twData := TTWData.Create();    
  webData := TWebData.Create();    

  //do stuff    
finally    
  webData.Free();    
  twData.Free();    
  newOrderSource.Free();    
end;    

Это работает, потому что Free() проверяет Self на nil.

Ответ 2

Как я уверен, все знают, стандартный способ управления объектом выглядит следующим образом:

A := TMyObject.Create;
try
  A.DoSomething;
finally
  A.Free;
end;

Если в TMyObject.Create есть исключение, тогда будет вызван деструктор, а затем возникнет исключение. В этом случае A не будет назначено.

Когда у вас есть несколько объектов, вы можете повторить шаблон:

A := TMyObject.Create;
try
  B := TMyObject.Create;
  try
    A.DoSomething;
    B.DoSomething;
  finally
    B.Free;
  end;
finally
  A.Free;
end;

Это очень быстро становится беспорядком и, следовательно, вопросом.

Стандартный трюк заключается в том, чтобы использовать тот факт, что Free можно смело вызывать на объектной ссылке nil.

A := nil;
B := nil;
try
  A := TMyObject.Create;
  B := TMyObject.Create;
  A.DoSomething;
  B.DoSomething;
finally
  B.Free;
  A.Free;
end;

У этого есть незначительная слабость, что он не устойчив к исключениям, возникающим в B.Free, но неразумно рассматривать это как условие отказа, которое можно игнорировать. Деструкторы не должны создавать исключения. Если они это сделают, значит, ваша система, вероятно, будет беспорядочно сломана.

Этот шаблон выше может стать немного грязным, поскольку добавлено больше объектов, поэтому я лично использую следующие вспомогательные методы.

procedure InitialiseNil(var Obj1); overload;
procedure InitialiseNil(var Obj1, Obj2); overload;
procedure InitialiseNil(var Obj1, Obj2, Obj3); overload;

procedure FreeAndNil(var Obj1); overload;
procedure FreeAndNil(var Obj1, Obj2); overload;
procedure FreeAndNil(var Obj1, Obj2, Obj3); overload;

На самом деле мой код имеет версии с еще большим количеством параметров. Для простоты обслуживания этот код автоматически генерируется из короткого Python script.

Эти методы реализованы очевидным образом, например

procedure FreeAndNil(var Obj1, Obj2);
var
  Temp1, Temp2: TObject;
begin
  Temp1 := TObject(Obj1);
  Temp2 := TObject(Obj2);
  Pointer(Obj1) := nil;
  Pointer(Obj2) := nil;
  Temp1.Free;
  Temp2.Free;
end;

Это позволяет нам перезаписать образец выше:

InitialiseNil(A, B);
try
  A := TMyObject.Create;
  B := TMyObject.Create;
  A.DoSomething;
  B.DoSomething;
finally
  FreeAndNil(B, A);
end;