UFO ET IT

객체에 액세스하기 전에 "if Assigned ()"를 사용하지 않는 이유는 무엇입니까?

ufoet 2020. 12. 28. 21:44
반응형

객체에 액세스하기 전에 "if Assigned ()"를 사용하지 않는 이유는 무엇입니까?


이 질문은 내가 지금 몇 번 보았던 stackoverflow에 대한 사람들의 특정 의견의 연속입니다. 나는 Delphi를 가르쳐 준 개발자와 함께 사물을 안전하게 유지하기 위해 항상 if assigned()객체를 해제하기 전에 그리고 다른 다양한 작업을 수행하기 전에 확인 했습니다. 그러나 이제이 수표를 추가 하지 말아야한다고 들었습니다 . 이 작업을 수행하면 응용 프로그램이 컴파일 / 실행되는 방식에 차이가 있는지 또는 결과에 전혀 영향을 미치지 않는지 알고 싶습니다.

if assigned(SomeObject) then SomeObject.Free;

양식이 있고 양식을 만들 때 배경에 비트 맵 개체를 만들고 작업이 완료되면 해제한다고 가정 해 보겠습니다. 이제 내 문제는 언젠가는 잠재적으로 해제되었을 수있는 객체에 액세스하려고 할 때 많은 코드에이 검사를 적용하는 데 너무 익숙해 졌다는 것입니다. 필요하지 않을 때도 사용하고 있습니다. 철저히하고 싶다 ...

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FBitmap: TBitmap;
  public
    function LoadBitmap(const Filename: String): Bool;
    property Bitmap: TBitmap read FBitmap;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FBitmap:= TBitmap.Create;
  LoadBitmap('C:\Some Sample Bitmap.bmp');
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if assigned(FBitmap) then begin //<-----
    //Do some routine to close file
    FBitmap.Free;
  end;
end;

function TForm1.LoadBitmap(const Filename: String): Bool;
var
  EM: String;
  function CheckFile: Bool;
  begin
    Result:= False;
    //Check validity of file, return True if valid bitmap, etc.
  end;
begin
  Result:= False;
  EM:= '';
  if assigned(FBitmap) then begin //<-----
    if FileExists(Filename) then begin
      if CheckFile then begin
        try
          FBitmap.LoadFromFile(Filename);
        except
          on e: exception do begin
            EM:= EM + 'Failure loading bitmap: ' + e.Message + #10;
          end;
        end;
      end else begin
        EM:= EM + 'Specified file is not a valid bitmap.' + #10;
      end;
    end else begin
      EM:= EM + 'Specified filename does not exist.' + #10;
    end;
  end else begin
    EM:= EM + 'Bitmap object is not assigned.' + #10;
  end;
  if EM <> '' then begin
    raise Exception.Create('Failed to load bitmap: ' + #10 + EM);
  end;
end;

end.

이제 나는라는 새로운 사용자 지정 목록 개체를 도입하고있어 가정 해 봅시다 TMyList의를 TMyListItem. 물론이 목록의 각 항목에 대해 각 항목 개체를 생성 / 해제해야합니다. 항목을 생성하는 몇 가지 다른 방법과 항목을 파괴하는 몇 가지 다른 방법이 있습니다 (가장 일반적인 추가 / 삭제). 이 보호 기능을 여기에 두는 것이 아주 좋은 습관이라고 확신합니다 ...

procedure TMyList.Delete(const Index: Integer);
var
  I: TMyListItem;
begin
  if (Index >= 0) and (Index < FItems.Count) then begin
    I:= TMyListItem(FItems.Objects[Index]);
    if assigned(I) then begin //<-----
      if I <> nil then begin
        I.DoSomethingBeforeFreeing('Some Param');
        I.Free;
      end;
    end;
    FItems.Delete(Index);
  end else begin
    raise Exception.Create('My object index out of bounds ('+IntToStr(Index)+')');
  end;
end;

많은 시나리오에서 적어도 나는 그것을 해제하기 전에 객체가 여전히 생성되기를 바랍니다. 하지만 미래에 어떤 일이 일어날 지 알 수 없습니다. 나는 항상이 수표를 사용해 왔지만 이제는 안된다는 말을 들었고 여전히 그 이유를 이해하지 못합니다.


편집하다

다음은 내가이 일을하는 습관이있는 이유를 설명하기위한 예입니다.

procedure TForm1.FormDestroy(Sender: TObject);
begin
  SomeCreatedObject.Free;
  if SomeCreatedObject = nil then
    ShowMessage('Object is nil')
  else
    ShowMessage('Object is not nil');
end;

내 요점은 해제 후 평가되지 않기 때문에 if SomeCreatedObject <> nil동일 하지 않습니다 . 따라서 두 가지 확인이 모두 필요합니다.if Assigned(SomeCreatedObject)SomeCreatedObjectnil


이것은 다양한 각도에서 매우 광범위한 질문입니다.

Assigned기능 의 의미

귀하의 질문에있는 대부분의 코드는 Assigned함수에 대한 잘못된 이해를 배반 합니다. 문서는 이 상태 :

nil (할당되지 않은) 포인터 또는 절차 적 변수를 테스트 합니다.

Assigned사용 하여 P가 참조하는 포인터 또는 프로 시저가 nil 인지 확인합니다 . P는 포인터 또는 절차 유형의 변수 참조 여야합니다.

할당 됨 (P) 은 포인터 변수의 경우 테스트 P <> nil , 절차 적 변수의 경우 @P <> nil 에 해당합니다.

할당 반환 거짓 P 인 경우 무기 호 , , 그렇지.

: 과제에 대한 개체 이벤트와 절차를 테스트 할 때, 당신이 테스트 할 수 없습니다 전무 및 사용 할당 올바른 방법입니다.

....

참고 : Assigned 는 매달린 포인터, 즉 nil 이 아니지만 더 이상 유효한 데이터를 가리 키지 않는 포인터를 감지 할 수 없습니다 .

의 의미는 Assigned포인터 및 절차 변수 에 따라 다릅니다. 이 답변의 나머지 부분에서는 질문의 컨텍스트이기 때문에 포인터 변수 만 고려할 것입니다. 개체 참조는 포인터 변수로 구현됩니다.

문서에서 취해야 할 요점은 포인터 변수의 경우 다음과 같습니다.

  1. Assigned테스트와 동일합니다 <> nil.
  2. Assigned 포인터 또는 개체 참조가 유효한지 여부를 감지 할 수 없습니다.

이 질문의 맥락에서 이것이 의미하는 것은

if obj<>nil

if Assigned(obj)

완전히 교환 가능합니다.

Assigned전화하기 전에 테스트Free

의 구현 TObject.Free은 매우 특별합니다.

procedure TObject.Free;
begin
  if Self <> nil then
    Destroy;
end;

이렇게하면 효과가없는 Free개체 참조 를 호출 할 수 있습니다 nil. 그만한 가치가 있기 때문에 나는 그러한 트릭이 사용되는 RTL / VCL의 다른 위치를 알고 있습니다.

객체 참조에서 Free호출 을 허용하려는 이유는 nil생성자와 소멸자가 Delphi에서 작동하는 방식에서 비롯됩니다.

생성자에서 예외가 발생하면 소멸자가 호출됩니다. 이는 성공한 생성자의 해당 부분에 할당 된 리소스를 할당 해제하기 위해 수행됩니다. Free구현되지 않은 경우 소멸자는 다음과 같이 표시되어야합니다.

if obj1 <> nil then
  obj1.Free;
if obj2 <> nil then
  obj2.Free;
if obj3 <> nil then
  obj3.Free;
....

퍼즐의 다음 부분은 Delphi 생성자가 인스턴스 메모리를 0으로 초기화한다는 것 입니다. 즉, 할당되지 않은 개체 참조 필드는 nil.

이 모든 것을 합치면 소멸자 코드가

obj1.Free;
obj2.Free;
obj3.Free;
....

후자의 옵션은 훨씬 더 읽기 쉽기 때문에 선택해야합니다.

참조가 소멸자에 할당되었는지 테스트해야하는 시나리오가 하나 있습니다. 객체를 파괴하기 전에 객체에 대한 메서드를 호출해야하는 경우 해당 객체의 가능성을 방지해야합니다 nil. 따라서이 코드가 소멸자에 나타나면 AV의 위험이 있습니다.

FSettings.Save;
FSettings.Free;

대신 당신은

if Assigned(FSettings) then
begin
  FSettings.Save;
  FSettings.Free;
end;

Assigned소멸자 외부에서 테스트

또한 소멸자 외부에서 방어 코드를 작성하는 것에 대해서도 이야기합니다. 예를 들면 :

constructor TMyObject.Create;
begin
  inherited;
  FSettings := TSettings.Create;
end;

destructor TMyObject.Destroy;
begin
  FSettings.Free;
  inherited;
end;

procedure TMyObject.Update;
begin
  if Assigned(FSettings) then
    FSettings.Update;
end;

In this situation there is again no need to test Assigned in TMyObject.Update. The reason being that you simply cannot call TMyObject.Update unless the constructor of TMyObject succeeded. And if the constructor of TMyObject succeeded then you know for sure that FSettings was assigned. So again you make your code much less readable and harder to maintain by putting in spurious calls to Assigned.

There is a scenario where you need to write if Assigned and that is where the existence of the object in question is optional. For example

constructor TMyObject.Create(UseLogging: Boolean);
begin
  inherited Create;
  if UseLogging then
    FLogger := TLogger.Create;
end;

destructor TMyObject.Destroy;
begin
  FLogger.Free;
  inherited;
end;

procedure TMyObject.FlushLog;
begin
  if Assigned(FLogger) then
    FLogger.Flush;
end;

In this scenario the class supports two modes of operation, with and without logging. The decision is taken at construction time and any methods which refer to the logging object must test for its existence.

This not uncommon form of code makes it even more important that you don't use spurious calls to Assigned for non-optional objects. When you see if Assigned(FLogger) in code that should be a clear indication to you that the class can operate normally with FLogger not in existence. If you spray gratuitous calls to Assigned around your code then you lose the ability to tell at a glance whether or not an object should always exist.


Free has some special logic: it checks to see whether Self is nil, and if so, it returns without doing anything -- so you can safely call X.Free even if X is nil. This is important when you're writing destructors -- David has more details in his answer.

You can look at the source code for Free to see how it works. I don't have the Delphi source handy, but it's something like this:

procedure TObject.Free;
begin
  if Self <> nil then
    Destroy;
end;

Or, if you prefer, you could think of it as the equivalent code using Assigned:

procedure TObject.Free;
begin
  if Assigned(Self) then
    Destroy;
end;

You can write your own methods that check for if Self <> nil, as long as they're static (i.e., not virtual or dynamic) instance methods (thanks to David Heffernan for the documentation link). But in the Delphi library, Free is the only method I know of that uses this trick.

So you don't need to check to see if the variable is Assigned before you call Free; it already does that for you. That's actually why the recommendation is to call Free rather than calling Destroy directly: if you called Destroy on a nil reference, you would get an access violation.


Why you shouldn't call

if Assigned(SomeObject) then 
  SomeObject.Free;

Simply because you would execute something like this

if Assigned(SomeObject) then 
  if Assigned(SomeObject) then 
    SomeObject.Destroy;

If you call just SomeObject.Free; then it's just

  if Assigned(SomeObject) then 
    SomeObject.Destroy;

To your update, if you afraid of the object instance reference use FreeAndNil. It will destroy and dereference your object

FreeAndNil(SomeObject);

It's similar as if you call

SomeObject.Free;
SomeObject := nil;

I'm not completely sure of that, but it seems:

if assigned(object.owner) then object.free 

works fine. In this example it would be

if assigned(FBitmap.owner) then FBitmap.free

ReferenceURL : https://stackoverflow.com/questions/8548843/why-should-i-not-use-if-assigned-before-accessing-objects

반응형