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);
}