블로그 이미지
Every unexpected event is a path to learning for you. blueasa

카테고리

분류 전체보기 (2797)
Unity3D (853)
Programming (479)
Server (33)
Unreal (4)
Gamebryo (56)
Tip & Tech (185)
협업 (61)
3DS Max (3)
Game (12)
Utility (68)
Etc (98)
Link (32)
Portfolio (19)
Subject (90)
iOS,OSX (55)
Android (14)
Linux (5)
잉여 프로젝트 (2)
게임이야기 (3)
Memories (20)
Interest (38)
Thinking (38)
한글 (30)
PaperCraft (5)
Animation (408)
Wallpaper (2)
재테크 (18)
Exercise (3)
나만의 맛집 (3)
냥이 (10)
육아 (16)
Total
Today
Yesterday

좀 긴가민가했던 .NET core 관련 내용을 정리하다가, 올바른 unmanaged 리소스 청소법을 위한 '지저분하기 짝이 없는, 그러나 반드시 알아야 하는' Finalize, IDisposable에 다다랐는데, 흥미롭게도 C# 쪽 pattern과 C++/CLI 쪽 pattern이 (적어도 표면 상으로는) 완연히 다르다는 사실을 발견.
본 사항은 정상적 application 구현을 위해서는 반드시 숙지해야 할 내용인데, 언어 별로 그리도 달라서야 원. 게다가, C++/CLI 쪽 MSDN 설명은 뭔가 하나 빠진 듯 하여 다 읽고 나서도 제대로 이해가 가질 않는다. 언어 별로 따로 익혀야 하는 것도 거시기한데, 설명이라도 제대로 해야지.

먼저, C#쪽 pattern. MSDN에 떡하니 올라와 있는 정형화된 pattern이다.

// 기반 클래스에서의 구현 pattern
public class Base: IDisposable
{
  
public void Dispose()
   {
    
 Dispose(true);


  // GC가 Finalize 호출하지 않도록 (중복호출 배제)

      GC.SuppressFinalize(this);
   }
 

   // disposing 플래그를 통해 Finalize에서 managed 리소스

   // 정리하지 않도록(해당 리소스는 GC 정리할 것임)

   protected virtual void Dispose(bool disposing)
   {
     
if (disposing)
      {
        
// Managed 리소스 정리
      }
     
// Unmanaged 리소스 정리
   }

  
// C# 소멸자. Finalize 메서드임
   ~Base()
   {
     
// 단순히 Dispose(false).
      Dispose (
false);
   }
}
// 파생 클래스에서의 구현 pattern
public class Derived: Base
{  
  
protected override void Dispose(bool disposing)
   {
     
if (disposing)
      {
        
// managed 리소스의 정리
      }
     
// Unmanaged 리소스 정리

      // 부모 개체의 리소스를 정리하도록

      base.Dispose(disposing);
   }
  
// 파생 클래스에서는 소멸자 정의를 하지 않음(부모 소멸자

   // 에서 재정의된 Dispose 호출할 것이므로)

}

암만 봐도 복잡하기 짝이 없는 패턴. 하지만 이보다 더 단순한 패턴을 내 머리로 만들어낼 궁리는 안한다(나올 가능성도 거의 없겠지만). 다음은 상기 사항에 대한 C++/CLI 쪽 pattern. 이 역시 MSDN에 명시된 내용이다.

ref class A {
   // Dispose() 해당하는 소멸자. delete를 통해 명시적 호출 가능.

   // Native C++ 가상 소멸자와 동일한 행동 양식

     // (스택 기반 semantic 개체 생성 , 자동 호출됨)

   ~A() {
      // managed 리소스 제거

        // ...

      // finalizer 통한 unmanaged 리소스 제거
      this->!A();

   }

   // Finalize에 해당하는 Finalizer
   !A() {

 // unmanaged 리소스 제거

   }
};

MSDN에는 몇몇 설명으로 위 C++/CLI의 패턴을 설명하다 마무리 짓는데, 상당히 난감해진다. 패턴은 왜 달라지는지, 달라지면서 없어진 GC.SupressFinalize(), Dispose(bool)은 어디로 갔는지 등에 대한 설명은 없거나 부실하다. 게다가 파생 클래스에 대한 언급은 아예 없어 과연 위 내용이 올바른 내용인가하는 의심까지 들 정도.

다음은 위 패턴에 대한 MSDN에 없는 내용으로서, 이와 같은 의문을 해소할 key가 되는 사항이다(C++/CLI의 기본 개념에 대해서는 MSDN 및 C++/CLI 소개글(번역문 링크) 참조).
1. C++/CLI에서의 소멸자는 virtual 키워드가 없더라도 무조건 가상 함수이다.

2. finalizer의 가시성 범위는 accessor가 있건 없건 private이다.

3. destructor와 finalizer가 IDisposable::DisposeFinalize()를 완전 대체하지는 않는다. 컴파일러는 IL 코드 내에 Dispose()Finalize()를 따로 삽입하며, 각기 내부에서 destructor와 finalizer를 적절히 호출한다.

4. destructor가 호출되면 finalizer는 호출되지 않는다. 이는 IL 코드 내 Dispose() 구현에서 GC.SupressFinalizer()를 호출하기 때문이다.

5. 위 코드를 기반으로 한 컴파일된 IL 코드는 C# 버전과 거의 흡사하다(Dispose(bool)을 통한 파생 클래스에서의 리소스 정리 등).

6. 위와 같은 내용을 기반으로, 파생 클래스에서 역시 위 패턴과 동일하게 작성하면 된다(destructor 또는 Finalizer 등에서 base 클래스의 destructor/finalizer 명시적 호출 등 부가적 행동 불필요).
위의 결론은 김형준님의 C++/CLI의 Dispose Pattern에 대한 고찰과 유사한 실험 및 생성된 IL 코드 분석을 통해 이루어졌다.

반응형
Posted by blueasa
, |