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의 기본적 동적 객체의 할당과 해제 방법이며 모든 동적 객체는 이를 사용해 구현하길 권장합니다.



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