LOGIN • JOININ

OBJECT MANAGEMENT

[Tutorials/[Common] 1. Object System/Tutorial_Object01_ObjectSystem]



1... Reference Counting and Garbage Collection

동적 할당된 객체는 스마트 포인터(Smart Pointer)로 원리로 동작합니다. 

즉 참조계수(Reference Count)가  0이 되는 순간 소멸처리를 수행합니다.

동적할당되는 객체는  ICGReferenceable을 상속받음으로써 객체 자체가 참조계수를 가질 수 있습니다.


2... ICGReferenceCount

참조계수를 사용하기 위해서는 ICGReferenceable 인터페이스 클래스를 상속받아야 합니다.

그것도 반드시 virtual public 상속으로 말입니다.

class foo : virtual public ICGReferenceable { public: foo() { printf("객체생성!\n");} virtual ~foo() { printf("객체파괴!\n");} private: virtual void ProcessDispose() { delete this;} };

ICGReferenceable를 상속받은 객체는 AddRef()와 Release()함수를 호출하여 참조계수를 증가시키거나 감소시킬 수 있습니다.

새로운 참조가 되면 AddReference()를 호출해 참조계수를 증가시키고 참조가 해제되면 Release()함수를 호출해 참조계수를 줄입니다.

또  Release()함수를 참조계수가 0이 되는 순간 OnFinalRelease()함수와 ProcessDispose()함수가 호출됩니다.

이 ProcessDispose()함수를 재정의함으로써 가비지 콜렉션 처리를 정의할 수 있습니다.

또 OnFinalRelese()함수는 소멸자와 같은 역할을 부여하는데 사용할 수 있습니다.

위의 예제의 ProcessDispose()함수에서는  ‘delete this’로 스스로를 할당 해제되도록 정의했습니다.



3... CGPTR<T>

참조계수를 사용하여 할당해제를 하려면 참조될 때마다 AddRef(), 참조가 해제될 때마다 Release()를 수동으로 해주게 되면 번거러울뿐만 아니라 오류를 만들 수도 있습니다.
이 참조계수의 증감을 자동으로 처리해 주는 스마트포인터 클래스가 바로 'CGPTR<T>'입니다.
'CGPTR<T>'은 std::shared_ptr<T>와 거의 유사하게 동작하지만  CGPTR<T>는 자체적으로 참조계수를 가진 객체 즉 ICGReferenceable를 상속받은 객체의 AddRef()와 Release()를 호출해준다는 것과 가비지콜렉션처리도 ProcessDispose()를 호출해 객체 자체에 일임한다는 것입니다.

void Test() { CGPTR<foo> pObject = new foo(); ... }

foo객체를 new로 동적할당해서 CGPTR<foo>에 대입시켰습니다. 

처음 생성될 때 참조계수는 0이며 대입되는 순간 참조계수를 자동적으로  증가시켜주어  1이 됩니다.
또 Test()함수의 종료되는 순간 CGPTR<foo>객체가 소멸하며 참조계수를 1감소시켜 참조계수는 0이 됩니다.
이때 pObject 객체의 ProcessDispose() 함수가 호출됩니다.



4... NEW<T>

ICGReferenceable 인터페이스 클래스를 상속받아 사용하려면 매번 ProcessDispose()함수를 재정의해야 합니다.
아래처럼 foo클래스는 ICGRefereceable를 상속받았지만 순수가상함수인 ProcessDispose()함수는 정의하지 않는다면 'new' 로는 객체로 생성하려하면 에러를 낼 것입니다.

class foo : virtual public ICGReferenceable { public: foo() { printf("객체생성!\n");} virtual ~foo() { printf("객체파괴!\n");} };


하지만 NEW<T>()함수를 사용하면 ProcessDispose()를 자동적으로 구현해주어 생성이 가능합니다.

void Test() { CGPTR<foo> pObject = NEW<foo>(); ... }

즉 'ICGReferenceable'만 'virtual public'으로 상속받았다면 'NEW<T>'함수로 생성이 가능하다.


굳이 ProcessDispose()함수를 직접 구현하지 않고 CGPTR<T>와 NEW<T>를 사용하는 것이 CGCII의 기본적 동적 객체의 할당과 해제 방법이며 모든 동적 객체는 이를 사용해 구현하길 권장합니다.



5... CGOWN<T> (CGDK9이상...)

CGOWN<T>는 CGPTR<T>의 특화버전으로  객체에 대한 소유권을 가진 CGPTR<T>라고 할수 있습니다. 

CGOWN<T>객체가 소멸될 때 포인팅한 객체가 ICGDestroyable 인터페이스를 가지고 있으면 Destroy()를 호출해 줍니다.

CGOWN이 가진 소유권이란 쉽게 말해서 그 객체의 주인 스마트포인트 객체를 위미합니다.
하나의 객체는 하나의 CGOWN<T>객체이만 넣을 수 있으며 복수 개의 CGOWN<T>객체에 넣을 수는 없습니다.
다만 CGOWN<T>에 넣은 객체라도 소유권을 가지지 않은 스마트코인터인 CGPTR<T>에는 넣을 수 있습니다.
또 소유권을 가진 CGOWN<T>는 소멸 시 단순히 참조계수만 1줄여주는 것뿐만 아니라 ICGDestroyable의 Destroy()함수를 호출해 줍니다.

따라서 아래와 같은 특성이 있습니다.
 

1) CGOWN<T>에 대입된  객체는 다른 CGOWN<T>에 대입(Assignment)은 불허되며 오직  이동(move) 연산을 통해서만 이동만 가능합니다.
   CGOWN<T> 객체에는 한번에 한곳에만 넣을 수 있기 때문입니다.

CGPTR<foo> pA; CGOWN<foo> pB; CGOWN<foo> pC; pB = pA; // Error! CGPTR<foo>를 CGOWN<foo>로 대입 불가능합니다. pB = pC; // Error! CGOWN<foo>를 CGOWN<foo>로도 대입 불가능합니다. pA = pB; // Ok! CGOWN<foo>를 CGPTR<foo>로는 대입가능합니다.


2) CGOWN<T>에서 CGPTR<T>로의 대입은 가능하나 CGPTR<T>에서 CGOWN<T>로의 이동은 불가능합니다.


CGOWN<foo> pB; CGOWN<foo> pC; pB = pC; // Error! CGOWN<foo>를 CGOWN<foo>로도 대입 불가능합니다. pB = std::move(pC);// Ok! CGOWN간은 이동(move)만 가능합니다.


3) CGOWN<T>에 들어가는 객체는 NEW<T>대신  MAKE_OWN<T>함수로만 생성하여 할당가능합니다.

CGOWN<foo> pObject = NEW<foo>(); // Error!!! CGOWN<foo> pObject = MAKE_OWN<foo>(); // Ok!!


4) CGOWN<T> 객체가 소멸될 때 ICGDestroyable 인터페이스를 가지고 있다면 Destroy()함수를 호출해 줍니다.


class foo : virtual public ICGDestroyable { .... virtual void Destroy() override { printf("Destoryed!\n"); } .... }; void main() { CGOWN<foo> pA = MAKE_NEW<foo>(); ... // 이 블록이 끝나 CGOWN<foo>> pA 객체가 소멸되면 Destroy()함수가 호출되며 "Destroyed!"가 출력된다. }


CGOWN<T> 객체는 일반적으로 특정 시점에서 특정 객체를 Destroy해줘야하는 경우 사용합니다.
또 순환 참조에 따른 할당 미해제 문제를 해결하기 위해서도 사용됩니다.


[Tutorial_Object_ObjectSystem_Step1.cpp]

#include "stdafx.h" // foo객체를 ICGReferenceable를 상속받아 정의하였고 // Reference Count가 0이 되면 스스로 delete를 호출하여 할당해제하도록 정의하였다. class foo : virtual public ICGReferenceCount { public: foo() { printf("객체생성!\n");} virtual ~foo() { printf("객체파괴!\n");} private: virtual void ProcessDispose() { printf("참조완료!\n"); delete this;} }; void Tutorial_Step1() { // 1) 처음 foo을 생성한다. // - 처음 생산했다면 참조계수는 0일 것이다. // (처음 생성되었을 때는 참조계수가 0이라고 해도 ProcessDispose()가 호출되지 // 않는다.) foo* pObject = new foo(); // 출력) printf("pObject의 참조계수는 %d입니다.\n", pObject->GetReferenceCount()); // 2) 참조계수를 증가시킨다. // - AddRef()함수를 호출하면 참조계수가 증가한다. // - 따라서 참조계수는 1이 된다. pObject->AddRef(); // 출력) printf("pObject의 참조계수는 %d입니다.\n", pObject->GetReferenceCount()); // 3) 참조계수를 감소시킨다. // - Release()함수를 호출하면 참조계수가 감소한다. // 따라서 참조계수는 0이 된다. // - 참조계수가 0이 되면 foo의 가상함수인 ProcessDispose()가 호출된다. // - ProcessDispose()에서 정의된대로 pObject가 delete된다. pObject->Release(); // 출력) 출력하면 뻑난다. }


[Tutorial_Object_ObjectSystem_Step2.cpp]

#include "stdafx.h" class foo2 : virtual public ICGReferenceable { public: foo2() { printf("객체생성!\n"); } virtual ~foo2() { printf("객체파괴!\n"); } private: virtual void ProcessDispose() { printf("참조완료!\n"); delete this; } }; void Tutorial_Step2() { { // 1) 객체를 생성한다. // - new로 foo2 객체를 생성해서 CGPTR<foo2>에 넣는다. // - CGPTR<t>에 T의 포인터가 들어가면 T의 참조계수를 1 증가시킨다. // - 따라서 생성된 객체를 pObject에 넣으면 참조계수가 1이 된다. CGPTR<foo2> pObject = new <foo2>(); // 2) pObject가 소멸될 때... // - Block이 닫히는 순간 CGPTR<foo2> 객체가 소멸되며 pObject의 // Release()를 호출해준다. // - 이때, pObject에 들어 있는 객체의 참조계수가 0이 되며 ProcessDispose()함수가 // 호출된다. // - CGReleaser::NDelete를 상속받았으므로 ProcessDispose()함수가 호출되면 // pObject가 가진 객체는 delete된다. } // Declare) CGPTR<foo2> pObjectA; { // 1) 객체를 생성했다. CGPTR<foo2> pObject = new<foo2>(); // 2) pObjectA에 pObject를 대입한다. // - 이렇게 하면 객체의 포인터가 복사되며 // 참조계수가 다시 1증가해서 참조계수가 2가 된다. pObjectA = pObject; // 3) pObject가 소멸될 때... // - Block이 닫히는 순간 지역변수인 CGPTR<foo2> pObject객체가 // 소멸되며 CGPTR<foo2>의 소멸자가 호출된다. // (foo2의 소멸자가 호출되는 것이 아니다!!!) // - 이때 foo2 객체의 Release()가 호출되며 참조계수가 줄어들어 1이 된다. // - 참조계수는 아직 1인 상태라 CTestObject의 ProcessDispose()가 아직 // 호출되지는 않는다. } // 4) pObjectA가 소멸될 때... // pObjectA가 소멸되며 참조계수를 1줄여 0이 되고 이때 ProcessDispose()함수가 // 호출되며 객체가 소멸된다. }


[Tutorial_Object_ObjectSystem_Step3.cpp]

#include "stdafx.h" // foo3 ICGReferenceable의 순수가상함수인 ProcessDispose()함수가 정의되지 않았고 // Releaser를 상속받지도 않았다. // 따라서 이 클래스를 동적이든 정적이든 어떤 객체로도 생성할 수 없다. // NEW<t>로 동적 생성할 수 있고 CGOBJ<t>로 정적 생성할 수 있다. class foo3 : virtual public ICGReferenceable { public: foo3() { printf("객체생성!\n"); } foo3(int a) { printf("%d의 초기값으로 객체생성!\n", a); } virtual ~foo3() { printf("객체파괴!\n"); } }; // 1) 전역변수로 foo3를 생성했다. // (전역변수이므로 프로그램이 종료되면서 소멸된다.) CGOBJ<foo3> g_Object; // 2) 동적변수로 foo3를 생성했다. // (동적생성된 foo3는 CGPTR<t>에 넣어져 참조계수가 1이 되며 // 프로그램이 종료되면서 전역변수인 g_refObject가 파괴되면서 // 동적할당된 객체는 delete되어 할당해제된다.) CGPTR<foo3> g_refObject = NEW<foo3>(); void Tutorial_Step3() { // 3) 지역변수로 foo3를 생성했다. // - 지역변수이므로 이 함수가 종료되면 소멸된다. CGOBJ<foo3> object; // 4) 동적으로 foo3를 생성한다. // - 이 객체는 Tutorial_Step4함수가 종료되면 소멸되며 // 내부 포인터에 Release가 호출되며 참조계수가 0이 된다. // 이때 ProcessDispose()에서 객체를 할당해제하게 된다. CGPTR<foo3> temprefObject = NEW<foo3>(10); }