Delphi FreeAndNil: Ищу альтернативную реализацию - PullRequest
3 голосов
/ 20 июня 2020

ПРИМЕЧАНИЕ: Потерпите меня, я чувствую себя немного «зажаренным пламенем» из-за некоторых обсуждений здесь и здесь и некоторых проблем, о которых я сообщил здесь и здесь .

Какой-то фон

Старый (до 10.4) FreeAndNil выглядел так:

FreeAndNil(var SomeObject)

Новое и fre sh FreeAndNil выглядит так:

FreeAndNil(const [ref] SomeObject: TObject);

ИМО у обоих есть свои недостатки:

  • У старого нет выполнять любую проверку типов, поэтому вызов FreeAndNil для указателей, записей и интерфейсов компилируется нормально, но дает интересные, но обычно нежелательные эффекты во время выполнения. (Сходит с ума или, если вам повезет, останавливается с EAccessViolation, EInvalidOperation et c.)
  • Новый принимает параметр const и, следовательно, любой объект. Но тогда предоставленный указатель объекта фактически изменяется с использованием некоторого хакерского кода .
  • Теперь вы можете вызвать новый FreeAndNil следующим образом: FreeAndNil(TObject.Create), и он будет компилироваться и даже запускаться просто хорошо. Мне понравился старый FreeAndNil, который предупреждал меня, когда я ошибся, и предоставлял, например, свойство вместо поля. Не знаю, что произойдет, если вы предоставите свойство типа объекта этой реализации FreeAndNil. Не пробовал.

Если мы изменим подпись на FreeAndNil(var SomeObject:TObject), то это не позволит нам передать какой-либо другой тип переменной, а именно тип TObject. Что также имеет смысл, как если бы это не было FreeAndNil, можно было бы легко изменить переменную, указанную как тип TComponent в подпрограмме, заменив переменную var на объект совершенно другого типа, например TCollection. Конечно, FreeAndNil этого не сделает, так как он всегда меняет параметр var на nil.

Таким образом, FreeAndNil становится особым случаем. Может быть, даже достаточно особенным, чтобы убедить delphi, чтобы добавить компилятор magi c FreeAndNil реализацию? Кто-нибудь голосует?

Возможный обходной путь

Я придумал приведенный ниже код в качестве альтернативы (здесь как вспомогательный метод, но также может быть частью TObject реализация), которая в своем роде объединяет оба мира. Assert поможет найти недопустимые вызовы во время выполнения.

procedure TSGObjectHelper.FreeAndNilObj(var aObject);
begin
  if Assigned(self) then
  begin
    Assert(TObject(aObject)=self,ClassName+'.FreeAndNil Wrong parameter provided!');
    pointer(aObject):=nil;
    Destroy;
  end;
end;

Использование будет примерно таким:

var MyObj:=TSOmeObject.Create;
...
MyObj.FreeAndNilObj(MyObj);

Я действительно тестировал эту процедуру, и она даже немного быстрее чем реализация 10,4 FreeAndNil. Наверное, потому что сначала провожу проверку назначений и напрямую звоню по номеру Destroy. Что я делаю , а не , так это то, что:

  • проверка типа происходит во время выполнения, и только если утверждения включены.
  • кажется, что необходимость передать одну и ту же переменную дважды. Что не обязательно верно / обязательно. Это должен быть тот же объект, а параметр должен быть переменной.

Другое расследование

Но было бы здорово, если бы можно было вызов без параметра

var MyObj:=TSomeObject.Create;
...
MyObj.FreeAndNil;

Итак, я испортил указатель self и смог установить его на nil, используя тот же код Hacky-Wacky, который 10.4 использует в своих FreeAndNil. Что ж ... это сработало внутри метода, self указал на nil. Но после такого вызова FreeAndNil переменная MyObj была не nil, а устаревшим указателем. (Это было то, что я ожидал.) Более того, MyObj может быть свойством или (результатом) процедуры, конструктором и т. Д. c.

, так что nope здесь тоже ...

И, наконец, вопрос:

Можете ли вы придумать более чистое / лучшее решение или трюк, который:

  • FreeAndNil (var aObject: TObject) с не очень строгой проверкой типов во время компиляции (возможно, директивой компилятора?), поэтому он позволяет компилировать и вызывать переменные любого типа объекта.
  • Жалуется на компиляцию время, когда передается что-то, что является не переменной / полем некоторого типа объекта
  • Помощь с описанием наилучшего решения / требования в RSP-29716

Ответы [ 2 ]

6 голосов
/ 20 июня 2020

Единственным подходящим решением для FreeAndNil, которое является типобезопасным и не позволяет освобождать результаты функций и свойства, было бы generi c параметр var:

 procedure FreeAndNil<T: class>(var Obj: T); inline;

Но в настоящее время Delphi компилятор не допускает обобщения для автономных процедур и функций https://quality.embarcadero.com/browse/RSP-13724

Тем не менее, это не означает, что вы не можете иметь общую реализацию c FreeAndNil, только то, что это будет немного больше

type
  TObj = class
  public
    class procedure FreeAndNil<T: class>(var Obj: T); static; inline;
  end;

class procedure TObj.FreeAndNil<T>(var Obj: T);
var
  Temp: TObject;
begin
  Temp := Obj;
  Obj := nil;
  Temp.Free;
end;

Вывод типа, введенный в Rio, позволит вам вызывать его без указания generi c signature:

TObj.FreeAndNil(Obj);

Вызов (и использование) generi c FreeAndNil в более старых Delphi версиях также возможен, но еще более подробный

TObj.FreeAndNil<TFoo>(Obj);
0 голосов
/ 22 июня 2020

Поскольку мы не можем создать глобальный procedure FreeAndNil<T:class>(var aObject:T), я бы предложил приведенный ниже код в качестве метода для класса TObject. (изменение rtl должно быть выполнено embarcadero)

class procedure TObject.FreeAndNil<T:class>(var SomeObject:T); // static; inline;
begin
  if Assigned(SomeObject) then
  begin
    var tmp:=SomeObject;
    SomeObject:=nil; // set to var nil before destroy, avoids use of try-finally
    tmp.Destroy; // no need to call Free, we already checked SomeObject is not nil
  end;
end;

и чтобы текущий (10.4 и ранее) FreeAndNil был удален из модуля sysutils, чтобы избежать двусмысленности.

Когда новый метод generi c FreeAndNil вызывается из любого другого метода, можно просто вызвать:

FreeAndNil(SomeObjectVariable)

и вывод типа 10.3+ позволяет избежать необходимости писать:

FreeAndNil<TMyClassSpec>(SomeObjectVariable)

, что приятно, потому что большая часть вашего кода будет скомпилирована без изменений.

В некоторых других местах, например глобальных подпрограммах и разделах initialization / finalization, нужно будет звоните:

TObject.FreeAndNil(SomeObjectVariable)

Что для меня было бы приемлемым и намного лучше, чем текущие и исторические промежуточные решения с FreeAndNil(const [ref] aObject:TObject) или нетипизированным FreeAndNil(var aObject)

И поскольку подпрограмма настолько проста, а производительность кажется проблемой, что можно было бы поспорить, что для нее есть реализация на ассемблере. Хотя я не уверен, что это разрешено / возможно для общих c, (и предпочтительно встроенных ) методов.

...