Search
Duplicate
🛑

C#과 C++/CLI의 Finalize, IDisposable Pattern 차이

Category
S/W 엔지니어
Tags
C#
C++/CLI
C++
Dispose Pattern
Finalize
Created time
2009/01/04
좀 긴가민가했던 .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#
복사
암만 봐도 복잡하기 짝이 없는 패턴. 하지만 이보다 더 단순한 패턴을 내 머리로 만들어낼 궁리는 안한다(나올 가능성도 거의 없겠지만). 다음은 상기 사항에 대한 C++/CLI 쪽 pattern. 이 역시 MSDN에 명시된 내용이다.
ref class A { // Dispose()에 해당하는 소멸자. delete를 통해 명시적 호출 가능. // Native C++의 가상 소멸자와 동일한 행동 양식(스택 기반 semantic 개체 생성 시, 자동 호출됨) ~A() { // managed 리소스 제거 // ... // finalizer를 통한 unmanaged 리소스 제거 this->!A(); } // Finalize에 해당하는 Finalizer !A() { // unmanaged 리소스 제거 } };
C#
복사
MSDN에는 몇몇 설명으로 위 C++/CLI의 패턴을 설명하다 마무리 짓는데, 상당히 난감해진다. 패턴은 왜 달라지는지, 달라지면서 없어진 GC.SupressFinalize(), Dispose(bool)은 어디로 갔는지 등에 대한 설명은 없거나 부실하다. 게다가 파생 클래스에 대한 언급은 아예 없어 과연 위 내용이 올바른 내용인가하는 의심까지 들 정도.
1.
C++/CLI에서의 소멸자는 virtual 키워드가 없더라도 무조건 가상 함수이다.
2.
finalizer의 가시성 범위는 accessor가 있건 없건 private이다.
3.
destructor와 finalizer가 IDisposable::Dispose()와 Finalize()를 완전 대체하지는 않는다. 컴파일러는 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 코드 분석을 통해 이루어졌다.
댓글 백업