C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 강좌/문서
C++Builder Programming Tutorial&Docments
[38] Owner와 Parent에 관한 진실
홍환민.행복 [hhshhm] 20869 읽음    2003-04-09 22:21
안녕하세요? 홍환민입니다.

보통 Owner와 Parent에 대해 다음과 같이 알고 있습니다.

Min 이라는 컴포넌트가 있고, 그 컴포넌트의 Owner가 Joo 라고 할 때,
다시 말해 코드 예를 보이자면.

TMin* Min = new TMin(Joo);

이렇게 하게 되면 Min이라는 객체의 소유자(Owner)가 Joo가 됩니다.

실제 소스를 보도록 하죠.

    FOwner: TComponent;
    property Owner: TComponent read FOwner;

constructor TComponent.Create(AOwner: TComponent);
begin
  FComponentStyle := [csInheritable];
  if AOwner <> nil then AOwner.InsertComponent(Self);
end;

TComponent에는 Owner 라는 프로퍼티가 있습니다.
그리고 생성자(Create)에서 소유자를 인자로 받아서 그 소유자의
InsertComponent를 호출합니다. 자기 자신을 인자로 주어서요.

추측을 해보자면, 소유자에는 자신이 소유한 하인(?) Component들의
목록을 담아두는 곳이 있을 것이고, 소유자의 InsertComponent를
호출함으로서 그 목록에다가 인자로 주어진 하인을 집어 넣는다.
이쯤 생각할 수 있습니다. 실제로 확인해 보죠.

FOwner도 TComponent이므로 (다시 말하면 TComponent 부터가 하인을 둘 수 있는 거군요. TComponent 부터 돈좀 벌었나 ;;;;)
TComponent에서 InsertComponent를 찾아보죠.

procedure TComponent.InsertComponent(AComponent: TComponent);
begin
  AComponent.ValidateContainer(Self);
  ValidateRename(AComponent, '', AComponent.FName);
  Insert(AComponent);
  AComponent.SetReference(True);
  if csDesigning in ComponentState then
    AComponent.SetDesigning(True);
  Notification(AComponent, opInsert);
end;

위에 보면 Insert(AComponent); 라는게 있는데 이게 중요해 보이죠?
따라가 보죠.

procedure TComponent.Insert(AComponent: TComponent);
begin
  if FComponents = nil then FComponents := TList.Create;
  FComponents.Add(AComponent);
  AComponent.FOwner := Self;
end;

FComponents 라는게 보이네요. 이게 아까 추측한 그 하인 목록인가 봅니다.
위에 보면 그 하인 목록이 TList로 되어 있다는 것도 알 수 있겠네요.

자, 이제 소유자가 파괴될 때 (메모리에서 해제될 때), 나혼자 죽기 시러~~~를 외치며
자기 꼬봉들도 같이 죽게 하는지 함 확인해 보죠.

Destroy 메서드를 보면 되겠죠?

destructor TComponent.Destroy;
begin
  Destroying;
  if FFreeNotifies <> nil then
  begin
    while Assigned(FFreeNotifies) and (FFreeNotifies.Count > 0) do
      TComponent(FFreeNotifies[FFreeNotifies.Count - 1]).Notification(Self, opRemove);
    FreeAndNil(FFreeNotifies);
  end;
  DestroyComponents;
  if FOwner <> nil then FOwner.RemoveComponent(Self);
  inherited Destroy;
end;

DestroyComponents; 라는게 보이네요.
그리고
  if FOwner <> nil then FOwner.RemoveComponent(Self);
주인님 나 지금 죽으니 돌아가실때 나 또 죽이지 마여~~ 하면서
소유자의 하인목록에서 자신을 제외시키는군요.

DestroyComponents 메서드를 따라가보죠.

procedure TComponent.DestroyComponents;
var
  Instance: TComponent;
begin
  while FComponents <> nil do
  begin
    Instance := FComponents.Last;
    if (csFreeNotification in Instance.FComponentState)
      or (FComponentState * [csDesigning, csInline] = [csDesigning, csInline]) then
      RemoveComponent(Instance)
    else
      Remove(Instance);
    Instance.Destroy;
  end;
end;

FComponents에 있는 하인들의 재산을 몰수하고 (메모리에서 해제하고)
목록에서 제외시켜 버리는게 보이죠?

자... 이로써, 소유자가 메모리에서 해제될 때, 딸린 꼬봉들도 함께 해제된다는 사실을 알 수 있었습니다.

자, 이제 Parent 프로퍼티에 대해 알아보도록 하죠.

Min->Parent = MyRoom;

이렇게 하게 되면 Min 이라는 놈이 MyRoom이라는 컴포넌트 위에 나타나서 화면에 보이게 됩니다.

다른 예를 들면 라디오버튼의 부모(Parent)로 패널을 설정하게 되면 그 라디오 버튼이 패널위에 나타나게 됩니다.

Owner는 나 XX 주인님과 같이 죽을래요.. 라고 설정하는 거라면,
Parent는 나 XX 컴포넌트 위에 나타날래요~

라는 것입니다. 실제 나타나는 것도 그렇게 좌표계도 그 Parent를 중심으로 계산됩니다.
부모가 Visible이 false가 되어서 안보이게 되면, 자식(??)도 안보입니다.
머 이런 겉모양에 관련된 것이라는 거죠.

이제 또 실제 소스를 보면서 확인해 보죠.

    FParent: TWinControl;
    property Parent: TWinControl read FParent write SetParent;

이렇게 되어 있네요. SetParent를 보죠.

procedure TControl.SetParent(AParent: TWinControl);
begin
  if FParent <> AParent then
  begin
    if AParent = Self then
      raise EInvalidOperation.CreateRes(@SControlParentSetToSelf);
    if FParent <> nil then
      FParent.RemoveControl(Self);
    if AParent <> nil then
    begin
      AParent.InsertControl(Self);
      UpdateAnchorRules;
    end;
  end;
end;

이미 부모가 지정되어 있었다면 (if FParent <> nil then) 그 부모의 RemoveControl을 부르네요.
나 이제 니 자식 안할래... 이러는거죠.. (이런 호로자쉭~!)

AParent.InsertControl(Self);
그리고 인자로 주어진 AParent한테 나 이제 당신을 부모로 섬길래요..
라고 합니다.

자, 이제 InsertControl을 보기로 하죠.
AParent가 TWinControl이었으니까 그리 가서 확인해 보면...

procedure TWinControl.InsertControl(AControl: TControl);
begin
  AControl.ValidateContainer(Self);
  Perform(CM_CONTROLLISTCHANGE, Integer(AControl), Integer(True));
  Insert(AControl);
  if not (csReading in AControl.ComponentState) then
  begin
    AControl.Perform(CM_PARENTCOLORCHANGED, 0, 0);
    AControl.Perform(CM_PARENTFONTCHANGED, 0, 0);
    AControl.Perform(CM_PARENTSHOWHINTCHANGED, 0, 0);
    AControl.Perform(CM_PARENTBIDIMODECHANGED, 0, 0);
    if AControl is TWinControl then
    begin
      AControl.Perform(CM_PARENTCTL3DCHANGED, 0, 0);
      UpdateControlState;
    end else
      if HandleAllocated then AControl.Invalidate;
    AlignControl(AControl);
  end;
  Perform(CM_CONTROLCHANGE, Integer(AControl), Integer(True));
end;

자 이제는 Insert(AControl); 이걸 따라가 봅시다.

procedure TWinControl.Insert(AControl: TControl);
begin
  if AControl <> nil then
  begin
    if AControl is TWinControl then
    begin
      ListAdd(FWinControls, AControl);
      ListAdd(FTabList, AControl);
    end else
      ListAdd(FControls, AControl);
    AControl.FParent := Self;
  end;
end;

ListAdd 메서드들을 호출하는게 보이죠?
FWinControls는 TWinControl 형으로 되어있는 컴포넌트들을 담는 걸테고..
FTabList는 그 탭 순서를 기억하기 위한 것으로 보이고요.

TWinControl형이 아니면, FControls 라는데다가 넣네요.

    FTabList: TList;
    FControls: TList;
    FWinControls: TList;

이렇게 되어 있네요. 리스트죠. 위에서 보았던 FComponents랑 비슷한 방식입니다.

Min 컴포넌트의 소유자(Owner)를 지정하게 되면, 그 소유자의 하인 리스트에 Min이 추가되는거고요,
Min 컴포넌트의 부모(Parent)를 지정하게 되면, 그 부모의 자식 리스트에 Min이 추가되는 거죠..

Owner의 경우 파괴자에서 그 하인 리스트에 있는 넘들이 다 같이 죽여버렸는데요...

과연, 부모는 어떻게 할까요? 자식인데... 설마 죽이지는 않겠죠??

자 봅시다.

destructor TWinControl.Destroy;
var
  I: Integer;
  Instance: TControl;
begin
  Destroying;
  if FDockSite then
  begin
    FDockSite := False;
    RegisterDockSite(Self, False);
  end;
  FDockManager := nil;
  FDockClients.Free;
  if Parent <> nil then RemoveFocus(True);
  if FHandle <> 0 then DestroyWindowHandle;
  I := ControlCount;
  while I <> 0 do
  begin
    Instance := Controls[I - 1];
    Remove(Instance);
    Instance.Destroy;
    I := ControlCount;
  end;
  FBrush.Free;
{$IFDEF LINUX}
  if FObjectInstance <> nil then WinUtils.FreeObjectInstance(FObjectInstance);
{$ENDIF}
{$IFDEF MSWINDOWS}
  if FObjectInstance <> nil then Classes.FreeObjectInstance(FObjectInstance);
{$ENDIF}
  inherited Destroy;
end;

위에서 이 부분이 보이십니까?

  I := ControlCount;
  while I <> 0 do
  begin
    Instance := Controls[I - 1];
    Remove(Instance);
    Instance.Destroy;
    I := ControlCount;
  end;

이해가 안가시는 분을 위해 몇가지 힌트를 더 드리죠.

    property ControlCount: Integer read GetControlCount;

function TWinControl.GetControlCount: Integer;
begin
  Result := 0;
  if FControls <> nil then Inc(Result, FControls.Count);
  if FWinControls <> nil then Inc(Result, FWinControls.Count);
end;

FControls 리스트에 있는거 개수 + FWinControls 리스트에 있는거 개수를 리턴하는 함수네요.

고로, 위 소스에서 ControlCount 이런 의미입니다.

    property Controls[Index: Integer]: TControl read GetControl;

function TWinControl.GetControl(Index: Integer): TControl;
var
  N: Integer;
begin
  if FControls <> nil then N := FControls.Count else N := 0;
  if Index < N then
    Result := FControls[Index] else
    Result := FWinControls[Index - N];
end;

Controls는 위와 같은 의미군요.

주어진 Index값에 따라 FControls와 FWinControls 리스트 내에 있는 항목들을 반환합니다.

위에 보았던 소스를 다시 뿌려보죠.

  I := ControlCount;
  while I <> 0 do
  begin
    Instance := Controls[I - 1];
    Remove(Instance);
    Instance.Destroy;
    I := ControlCount;
  end;

리스트를 루프 돌면서 Remove() 함수로 리스트에서도 지우고,
Instance.Destroy; 를 호출해서 메모리도 지워버립니다.

허걱... 부모가 자식을 죽였습니다 ㅡ,.ㅡ;;;;

이 글을 쓴 목적이자, 제가 말씀드리고 싶은게 바로 이 부분입니다.

일반적으로 Owner만 자기에게 딸린 넘들을 죽이는지 알고 있는데,
Parent도 죽입니다!!! 둘다 나쁜넘입니다!

그런데, 이 둘다 죽인다는 사실 때문에 한가지 생각해 볼 문제가 있습니다.

TControl은 TComponent에서 상속되어 집니다.

그렇다면 TControl은 TComponent에 있는 소유된 것들(하인) 목록인 TComponents와 TControl에 있는 자식 목록인 FControls + FWinControl를 둘다 갖게 될 것입니다.

예를 들어, Min이라는 컴포넌트가 소유자, 부모를 둘다 Joo라는 컴포넌트로
한다면, Joo의 소유된 것들 (하인) 목록에도 들어가고, 자식 목록에도 들어가게 됩니다.

이제 Joo가 파괴될 때, Joo의 파괴자에 의해서 소유된 것 목록에 있는 것들이 파괴될 테고,
자식 목록에  있는 것들도 파괴될 것입니다.

그러면.... Min을 두번 죽이는 거니까 이미 죽은 놈을 또 죽일려고 하면 걔 이미 죽인놈이야~~
하고 메모리 에러 (Access Violation)가 나겠죠?

그렇다면 지금 부모나 소유자 둘다 죽인다! 라는 사실 모르고, 소유자만 죽인다~ 라고 알고 쓰던 분들이
아무 문제 없이 사용하셨듯이, 문제가 없는데여..? 라고 할 수 있습니다.

그건 아래 소스를 보면 해소가 됩니다.

destructor TComponent.Destroy;
begin
  Destroying;
  if FFreeNotifies <> nil then
  begin
    while Assigned(FFreeNotifies) and (FFreeNotifies.Count > 0) do
      TComponent(FFreeNotifies[FFreeNotifies.Count - 1]).Notification(Self, opRemove);
    FreeAndNil(FFreeNotifies);
  end;
  DestroyComponents;
  if FOwner <> nil then FOwner.RemoveComponent(Self);
  inherited Destroy;
end;

TControl의 Destroy에서 TComponent의 Destroy가 호출이 되는데,
  if FOwner <> nil then FOwner.RemoveComponent(Self);
위와 같이 목록에서 지워줍니다.

destructor TWinControl.Destroy;
var
  I: Integer;
  Instance: TControl;
begin
  Destroying;
  if FDockSite then
  begin
    FDockSite := False;
    RegisterDockSite(Self, False);
  end;
  FDockManager := nil;
  FDockClients.Free;
  if Parent <> nil then RemoveFocus(True);
  if FHandle <> 0 then DestroyWindowHandle;
  I := ControlCount;
  while I <> 0 do
  begin
    Instance := Controls[I - 1];
    Remove(Instance);
    Instance.Destroy;
    I := ControlCount;
  end;
  FBrush.Free;
{$IFDEF LINUX}
  if FObjectInstance <> nil then WinUtils.FreeObjectInstance(FObjectInstance);
{$ENDIF}
{$IFDEF MSWINDOWS}
  if FObjectInstance <> nil then Classes.FreeObjectInstance(FObjectInstance);
{$ENDIF}
  inherited Destroy;
end;

이게 부모가 파괴될 때 호출되는 파괴자인데.. (똑같은 소스 또 뿌려서 길이만 늘어나 버리는 ㅡ,.ㅡ;;)
저~기 보이는 자식 놈들 죽이는 while 루프를 돌면서 각각의 Controls[x]는
TControl의 파괴자를 호출하고 그건 또 TComponent의 파괴자를 호출하므로..
고로 FComponents에는 이미 파괴된 자식놈들은 다 사라진 상태에서..
마지막 줄의 inherited Destroy;에 의해서 나머지 하인(소유된 것)들만 지워지죠...

그래서 두번 지워지는 일은 없습니다.

근데 이 얘기는 왜 이렇게 길께 썼냐... ???? ㅡ,.ㅡ;

첫째, Parent도 자식놈들 파괴를 하는데.. 그렇지 않은 착한 놈인지 아는 분들이 상당수인 것 같아서...
본인도 불과 얼마전까지는 모르고 있었고... 책에도 이런 얘기도 안나왔구...

둘째, 컴포넌트 제작시나 특별한 경우, 이 두번 죽인다는 사실로 인해 문제가 발생하는 경우가 가끔 있습니다.
이미 삭제된거 삭제한다고 메모리 참조 에러 날 경우를 만나게 됩니다.
저도 겪어 보았구요.. 근데 이 사실 모르면 해결하기 힘들겠죠?

흠.... 에혀~~ 쓰다보니 길어졌네요.
이만 줄입니다.

행복하세요~
만세~!

참고 ) 위에서 분석해본 VCL 소스는 델파이 7.0에 들어있는 소스입니다.

김윤동.제라툴 [zeratul]   2003-05-09 17:47 X
잼있군 ㅋ
방태윤 [nabty]   2008-09-03 22:18 X
그렇군 ㅋ

+ -

관련 글 리스트
38 Owner와 Parent에 관한 진실 홍환민.행복 20869 2003/04/09
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.