클릭하여 확대해 보시길… 첨언하지면, 그림의 “Longhorn”은 잘못된 표기입니다. Longhorn은 Window Vista의 서버버전의 제품명으로 의미변경 되었다고합니다. 꽤 오래전에 인터넷 상에서 받아 보던 것으로 자료가 옛날것이긴 하지만, 명칭 이외의 부분에 대해서는 거의 틀린 부분이 없기 때문에 Vista의 전체적인 아키텍쳐 구성 요소가 어떻게 되는지를 살펴보기에 용이할 것 같습니다.http://www.gisdeveloper.co.kr/?p=209&preview=true
C++/CLI의 Dispose Pattern에 대한 고찰
리소스 해제를 위한 .NET 개발환경에서 제공하는 Dispose 패턴을 파악하기 위해 테스트용으로 적용할 클래스 정의는 다음과 같으며 총 세가지의 경우로 시험을 해보았다.
ref class T : public IDisposable { public: T() { Console::WriteLine(L"T() invoked"); } ~T() { Console::WriteLine(L"~T() invoked"); } !T() { Console::WriteLine(L"!T() invoked"); } };
첫번째 시험 코드는 마치 지역변수처럼 할당하는 경우이다. 하지만 절대 지역 변수가 아니라는 점.. CLR Heap에 할당된다.
int main(array ^args) { T a; return 0; }
실행 결과는 다음과 같다. 지역변수처럼 변수의 유효 Scope를 벗어나는 순간 소멸자가 호출되었다. 실제 IL에 의해 구현된 내부 흐름은 소멸자 호출이 아니라 IDisposable::Dispose 매서드의 호출이다. 즉, 소멸자가 IDisposable::Dispose의 재구현이다. 또한 내부적으로 GC에 의해 호출되어져야했을 Finalize 매서드는 호출되지 않게 조치된다. C#과는 다르게 Finalize가 호출되지 않도록 자동화되었다는 점이 매우 특이하다.
T() invoked ~T() invoked
두번째 시험 코드는 C++에서는 포인터 개념으로 생각되는, 즉 C++/CLI는 Handle 개념으로 CLR의 Heap에 객체를 생성하였고 delete를 호출하지 않은 경우이다.
int main(array ^args) { T ^a = gcnew T(); return 0; }
실행 결과는 다음과 같다. Finalize에 해당하는 !T()가 호출되었다는 점에 유의하자.
T() invoked !T() invoked
세번째 시험 코드는 두번째와 다르게 delete 연산자를 적용해 주었다.
int main(array ^args) { T ^a = gcnew T(); delete a; return 0; }
실행 결과는 다음과 같다. Finalize가 아닌 Dispose가 호출되었다.
T() invoked ~T() invoked
여기서 얻을 수 있는 가장 중요한 한가지 결론은 ~T()에 해당하는 Dispose()와 !T()에 해당하는 Finalize()의 코드는 절대로 같이 호출되지 않는다는 점이다. C++/CLI의 사용자가 .NET의 참조형 변수를 지역변수처럼 사용하든지, gcnew에 의해 할당하여 사용하든지.. 또한 사용한 후 delete를 했든지, 하지 않았든지 간에 ~T()와 !T() 둘중에 하나는 반드시 실행되다는 점이다. ~T()는 기존의 C++ 개념으로써 호출되며 !T()는 .NET의 GC에 의해 호출된다. 즉, 리소스 해제를 위한 코드는 ~T()와 !T()에 똑 같이 중복적으로 와야한다고 생각한다.