Game Dev/Scrap

The Function Pointer Tutorial

AKer 2008. 8. 11. 17:51
반응형

원문 : http://alones.byus.net/moniwiki/wiki.php/function_pointer?action=show



// C
// C
int (*pt2Function)(float, char, char) = NULL;

// C++
int (TMyClass::*pt2Member)(float, char, char) = NULL;
int (TMyClass::*pt2ConstMember)(float, char, char) const = NULL;



함수 포인터 배열은 어떻게 사용하는가?

함수 포인터 배열의 동작은 매우 흥미롭다. 인덱스를 이용해서 함수를 선택할 수 있다. 구문은 복잡해 보이고 종종 혼란을 야기하기도 한다. 아래 코드에서 C와 C++에서 함수 포인터 배열을 정의하고 사용하는 두 가지 방법을 알 수 있을 것이다. 첫 번째 방법은 typedef를 사용하는 것이고, 두 번째 방법은 배열 선언을 직접하는 함수 포인터를 그대로 이용해서 하는 것이다. 어떤 방식을 선택하는 것은 프로그래머 취향에 달려있다.

// 타입 정의: 'pt2Function'은 이제 타입으로 사용될 수 있다.
typedef int (*pt2Function)(float, char, char);

// 함수 포인터 배열을 어떻게 사용하는지를 보여준다.
void Array_Of_Function_Pointers()
{
   printf("\nExecuting 'Array_Of_Function_Pointers'\n");

   // 배열을 정의하고 각 각을 NULL로 초기화한다. 는 [[br]]
   // int를 반환하고 float 하나와 두 개의 char를 인자로 받는 함수들에 대한 [[br]]
   // 10개의 포인터로 구성된 배열이다.

   // typedef를 이용한 첫 번째 방법
   pt2Function funcArr1[10] = {NULL};

   // 함수 포인터를 직접 사용하는 두 번째 방법
   int (*funcArr2[10])(float, char, char) = {NULL};


   // 함수 주소 대입 - 'DoIt'과 'DoMore'는 함수는 2.1-4에 정의되어 있는 것 [[br]]
   // 처럼 함수 시그너처가 적절한 함수이다.
   funcArr1[0] = funcArr2[1] = &DoIt;
   funcArr1[1] = funcArr2[0] = &DoMore;

   /* 좀더 대입 */

   // 함수 포인터 주소에 대한 인덱스를 이용해서 함수 호출
   printf("%d\n", funcArr1[1](12, 'a', 'b'));         //  간단한 방식
   printf("%d\n", (*funcArr1[0])(12, 'a', 'b'));      // "정확한" 호출 방법
   printf("%d\n", (*funcArr2[1])(56, 'a', 'b'));
   printf("%d\n", (*funcArr2[0])(34, 'a', 'b'));
}

// 타입 정의: 'pt2Member'는 이제 타입으로 사용될 수 있다.
typedef int (TMyClass::*pt2Member)(float, char, char);

// 멤버 함수 포인터들의 배열이 어떻게 동작하는지를 보여준다.
void Array_Of_Member_Function_Pointers()
{
	cout << endl << "Executing 'Array_Of_Member_Function_Pointers'" << endl;

	// 배열을 정의하고 NULL로 각 배열의 요소들을 초기화한다. 과 [[br]]
	// 는 int를 반환하고 float하나와 두 개의 char를 인자로 받는 [[br]]
	// 멤버 함수들에 대한 10개의 포인터로 구성된 배열이다.

	// typedef를 이용한 첫 번째 방법
	pt2Member funcArr1[10] = {NULL};

	// 함수 포인터를 그대로 사용하는 두 번째 방법
	int (TMyClass::*funcArr2[10])(float, char, char) = {NULL};

	// 함수 주소 대입 - 'Doit'과 'DoMore'는 2.1-4에 정의된 것 처럼 TMyClass의 [[br]]
	// 적절한 멤버 함수이다.
	// 첫 번째 방법은 typedef를 사용했고, 두 번째 방법은 함수 포인터를 그대로 [[br]]
	// 사용했다. 어떤 방법을 사용할지는 프로그래머의 취향에 달려 있따.
	funcArr1[0] = funcArr2[1] = &TMyClass::DoIt;
	funcArr1[1] = funcArr2[0] = &TMyClass::DoMore;

	/* 좀 더 대입 */

	// 멤버 함수 포인터를 가르키는 인덱스를 이용해서 함수 호출
	// note: 멤버 함수를 호출하기 위해 TMyClass의 인스턴스가 필요하다.
	TMyClass instance;
	cout << (instance.*funcArr1[1])(12, 'a', 'b') << endl;
	cout << (instance.*funcArr1[0])(12, 'a', 'b') << endl;
	cout << (instance.*funcArr2[1])(34, 'a', 'b') << endl;
	cout << (instance.*funcArr2[0])(89, 'a', 'b') << endl;
}



C와 C++ 함수 포인터를 은닉화 시키는 Functor

Functor는 상태를 가진 함수들이다. C++에서는 하나 이상의 private 멤버 함수를 이용해서 상태를 저장할 수 있고 재정의된 () 연산자를 통해서 함수를 수행할 수 있는 것을 알 것이다. Functor는 C와 C++ 함수 포인터를 템플릿과 다형성을 이용해서 은닉화 시킬 수 있다. 임의의 클래스들의 멤버 함수에 대한 포인터 목록을 구성할 수 있고 각 클래스나 인스턴스를 가르키는 포인터에 구애 받지 않고 동일한 인터페이스로 그 것들을 호출할 수 있다. 모든 함수들은 동일한 반환 타입과 파라미터를 가지기만 하면 된다. 가끔 Functor는 Closure로 불리기도 한다. Functor를 이용해서 콜백을 구현할 수도 있다.



Functor를 어떻게 구현하는가?

먼저 TFunctor라는 베이스 클래스가 필요한데, TFunctor는 Call 이라는 가상함수나 멤버 함수를 호출할 수 있는 연산자 ()을 제공한다. 재정의된 연산자를 사용하거나 Call과 같은 함수를 사용하는 것은 개인의 취향에 달렸다. 베이스 클래스로부터 템플릿 클래스 TSpecificFunctor를 상속 받는데, TSpecificFunctor는 생성자에서 오브젝트를 가르키는 포인터와 멤버 함수를 가르키는 포인터로 로 초기화된다. 상속 받은 클래스는 Call 또는 연산자 ()을 베이스 클래스로부터 재정의 한다 : 재정의된 버전에서 오브젝트와 멤버 함수에 대한 포인터들을 이용해서 멤버 함수를 호출한다. 함수 포인터 사용 법이 명확하지 않으면 원문 첫 부분에 있는 함수 포인터 소개를 다시 보기 바란다.

// abstract base class
// [alones] 멤버 함수가 모두 순수 가상 함수 (pure virtual function)
class TFunctor
{
public:

	// 멤버 함수를 호출할 두 가지 가능한 함수들
	// 가상 함수들을 통해서 오브젝트에 대한 포인터와 멤버 함수에 대한 [[br]]
	// 포인터를 사용할 상속 받은 클래스들이 함수 콜을 쓸 수 있다.
	virtual void operator()(const char* string)=0;  // 연사자를 이용한 호출
	virtual void Call(const char* string)=0;        // 함수를 이용한 호출
};


// 상속 받은 템플릿 클래스
template  class TSpecificFunctor : public TFunctor
{
private:
	void (TClass::*fpt)(const char*);   // 멤버 함수에 대한 포인터
	TClass* pt2Object;                  // 오브젝트에 대한 포인터

public:

	// 생성자 - 오브젝트에 대한 포인터와 멤버 함수에 대한 포인터를 취하며 [[br]]
	//          그 것들을 두 개의 prviate 변수에 저장한다.
	TSpecificFunctor(TClass* _pt2Object, void(TClass::*_fpt)(const char*))
	{ pt2Object = _pt2Object;  fpt=_fpt; };

	// 연산자 "()" 재정의
	virtual void operator()(const char* string)
	{ (*pt2Object.*fpt)(string);};              // 멤버 함수 수행

	// "Call" 함수 재정의 
	virtual void Call(const char* string)
	{ (*pt2Object.*fpt)(string);};             // 멤버 함수 수행
};



Functor 사용 예

다음 예제 반환 타입이 void이고 const char*를 파라미터로 필요로 하는 Display라는 함수를 제공하는 더미 클래스 두 개가 있다. TFunctor를 가르키는 두 개의 포인터를 가지는 배열을 생성하고 배열의 각 요소들을 TClassA와 TClassB의 멤버 함수에 대한 포인터와 오브젝트를 은닉하고 있는 TSpecificFunctor로 초기화 한다. 그리고 각 멤버 함수들을 호출하기 위해 functor-array를 사용한다. 함수 호출을 위해서 오브젝트에 대한 어떠한 포인터도 필요 없고 클래스에 구애 받지 않아도 된다.
// 더미 클래스 A
class TClassA
{
public:

	TClassA(){};
	void Display(const char* text) { cout << text << endl; };

	/* TClassA의 나머지 부분*/
};

// 더미 클래스 B
class TClassB
{
public:

	TClassB(){};
	void Display(const char* text) { cout << text << endl; };

	/* TClassB 나머지 부분 */
};


// main 프로그램
int main(int /*argc*/, char* /*argv[]*/)
{
	// 1. TClassA와 TClassB에 대한 인스턴스 생성
	TClassA objA;
	TClassB objB;


	// 2. TSpecificFunctor 인스턴스 생성 ...
	//    a ) TClassA의 오브젝트와 멤버에 대한 포인터를 은닉한 functor
	TSpecificFunctor specFuncA(&objA, &TClassA::Display);

	//    b ) TClassB의 오브젝트와 멤버에 대한 포인터를 은닉한 functor
	TSpecificFunctor specFuncB(&objB, &TClassB::Display);


	// 3. 베이스 클래스인 TFunctor에 대한 포인터의 배열 생성하고 초기화
	TFunctor* vTable[] = { &specFuncA, &specFuncB };


	// 4. 오브젝트 필요 없이 멤버 함수 호출을 위해 배열 사용
	vTable[0]->Call("TClassA::Display called!");        // "Call" 함수를 통해서
	(*vTable[1])   ("TClassB::Display called!");        // 연사자 "()"을 통해서


	// 종료를 위해 enter 키 치기
	cout << endl << "Hit Enter to terminate!" << endl;
	cin.get();

	return 0;
}



반응형

'Game Dev > Scrap' 카테고리의 다른 글

Developement Resource on the WEB  (0) 2008.10.07
Starcraft 2 Effects & Techniques  (0) 2008.08.20
Resource Management Best Practices  (0) 2008.07.23
Device Lost Handling  (0) 2008.07.23