이전 된 블로그 http://alones.kr

Alones world :: [번역 작업] The Function Pointer Tutorial (by Lars Haende)
 Lars Haende의 사이트에 있는 "The Function Pointer Tutorial"을 번역해보았습니다.
물론 아래 글 처럼 중간 중간에 이해를 돕기 위해 나의 의견과 실제 경험들을 조금 추가했습니다.

Function Pointer는 많이 사용되는 feature 중의 하나일 것입니다 (특히 C에서는). 하지만 저의 경우 일 전에 C++에서 클래스 멤버에 대해서 Function Pointer를 사용하면서 몇 가지 난관에 부딪혔었고 아래 Lars Haende의 글이 많은 도움을 주었습니다.

아래 Tutorial은 Fucntion Pointer에 대해 개념 정리에서 시작해서 Function Pointer의 선언, 사용, 파라미터로 사용, 함수 반환 값으로의 사용, C의 Function Pointer, C++에서 non-static member function과 static member function에 대한 Fnction Pointer 그리고 마지막으로 Functor 까지 두루 두루 예제와 함께 잘 다루고 있는 좋은 글입니다.

그리고 5장의 참고 사이트에 거론되어 있는 주옥 같은 사이트 목록 또한 값집니다.
(※ 시간이 되면 참고 사이트들에 대해서도 번역 작업을 할 계획입니다.)

※ 2007.05.08 현재 초안이기 때문에 오.탈자도 있을 것이고 잘 못된 부분도 있을 것입니다.
    블로그의 댓글이나 위키의 커멘트를 통해서 feed/back을 주시면 감사하겠습니다)


- 실제 아래 제 wiki site에서 보시는 것이 예제 코드를 보실 때 더 편할 것입니다.
    ({{{#!vim cpp로 쓴 것이 wiki에서는 깨끗하게 잘 정리되서 보이지만 blog로 복사하니 가로도 짤리고 세로도 검은색이 길어서 보기 불편합니다)
  ※ 물론 최신 사항은 아래 위키에서 보실 수 있습니다.

 in my wiki: http://alones.byus.net/moniwiki/wiki.php/function_pointer?action=show



The Function Pointer Tutorial


  • [http]http://www.newty.de/fpt/index.html 사이트의 function pointer에 대한 tutorial이 c~c++을 걸쳐 아주 자세한 내용까지 잘 나와있어서 번역을 하기로 했다.
  • 번역 중 [alones] 는 역자인 나의 설명 및 의견이다.
  • author: Lars Haende
  • 역자: alones
  • 초안: 2007-05-06 ~ 08


목차

1 함수 포인터 소개
1.1 함수 포인터란 무엇인가?
1.2 소개 예제 또는 switch 문을 대처하는 방법
2 C와 C++ 함수 포인터의 구문 (Syntax)
2.1 함수 포인터의 정의 (Define)
2.2 호출 규약 (Calling Convention)
2.3 함수 포인터 주소를 함수 포인터에 대입 (Assign an address to a Function Pointer)
2.4 함수 포인터 비교 (Comparing Function Pointers)
2.5 함수 포인터를 이용한 함수 호출
2.6 함수 포인터를 어떻게 아규먼트로 전달할 것인가?
2.7 함수 포인터를 어떻게 반환할 것이가?
2.8 함수 포인터 배열은 어떻게 사용하는가?
3 C와 C++에서 콜백 (Callback)을 어떻게 구현할 것인가?
3.1 콜백 함수에 대한 소개
3.2 C에서 어떻게 콜백을 구현할 것인가?
3.3 qsort를 사용하는 예제
3.4 static C++ 멤버 함수에 대한 콜백을 어떻게 구현할 것인가?
3.5 non-static C++ 멤버 함수에 대한 콜백을 어떻게 구현할 것인가?
4 C와 C++ 함수 포인터를 은닉화 시키는 Functor
4.1 Functor란 무엇인가?
4.2 Functor를 어떻게 구현하는가?
4.3 Functor 사용 예
5 주제 별 참고 사이트
5.1 함수 포인터 소개
5.2 콜백과 Functor
5.3 그외 여러가지
6 원문 update 현황

1 함수 포인터 소개 #

함수 포인터는 매우 흥미롭고 효율적이며 멋진 프로그래밍 기법이다. 이 것을 이용해서 switch/if문을 대처할 수 있고, 늦은 결합 (late-binding)을 이룰 수 있거나 콜백 (callbacks)을 구현할 수 있다. 불행하게도 - 아마 함수 포인터의 복잡한 구문 때문에 - 대부분의 컴퓨터 서적이나 문서에서는 함수 포인터를 좋지 않은 기법으로 다룬다. 설령 서적이나 문서에서 거론된다고 해도 아주 간단하게 또 피상적으로 다뤄질 뿐이다. 함수 포인터는 메모리를 할당하거나 해제하지 않기 때문에 일반 포인터 보다 에러를 덜 유발한다. 함수 포인터가 무엇이고 각 상황에서의 구문만 익히면 함수 포인터를 잘 사용할 수 있다. 하지만 함수 포인터를 사용할 때 "정말 이것이 필요한지"에 대해 항상 자문해보아야 할 것이다. 자신 만의 늦은 결합 (late-binding)을 구현하는 것도 멋진 일이겠지만 기존의 C++ structure를 사용하는 것이 코드의 가독성을 높이고 명료할 것이다.
늦은 결합 (late-binding)의 경우는 실행 시간 (runtime)이라는 관점이다:
가상 함수 (virtual function)을 호출 했을 때, 프로그램은 어느 함수가 불려져야 하는지를 결정해야 한다. 이 것은 매 호출 시 발생하는 비용이고 가상 함수를 이용하는 대신 함수 포인터를 이용해서 그 시간을 줄일 수 있을 것이다. 그렇지 않을 수도 있지만...
아무튼, 현재의 컴파일러들은 매우 우수하다. 나의 볼랜드 (Borland) 컴파일러에서 두 개의 float를 곱하는 가상 함수 하나를 호출하는 것을 함수 포인터를 이용해서 개선시킨 시간은 약 2% 정도였다.

1.1 함수 포인터란 무엇인가? #

함수 포인터는 포인터이다. 즉, 함수의 주소를 가르키고 있는 변수이다. 실행 중인 프로그램은 어떤 공간을 메모리에서 획득한다는 것을 명심해야 한다. 실행 가능한 컴파일된 프로그램 코드 ([alones] 코드 영역)와 사용된 변수들 ([alones 데이터 영역)은 모두 메인 메모리에 놓여진다. 따라서 프로그램 코드 내의 어떤 함수는 character field와 같이 하나의 주소에 불과하다. 여러 분이 또는 더 나아가 컴파일러/프로세서가 어떤 포인터가 가르키는 메모리를 어떻게 해석하느냐가 중요한 것이다.
[alones] 어떻게 보면 함수 포인터는 조금 복잡해보이는 함수를 위한 포인터로 볼 수도 있기에 위와 같이 말하는 것 같다.

1.2 소개 예제 또는 switch 문을 대처하는 방법 #

label이라고 불리는 특정 시점에서 DoIt()이라는 함수가 호출되어야 할 때, DoIt() 함수의 호출 문을 label의 시점에 두면 그만일 것이다. 코드를 컴파일하고 나면 프로그램이 label을 만날 때 마다 DoIt()은 매번 호출 될 것이다. 아무 이상이 없다. 하지만 빌드 타임 (build-time)에 어느 함수가 불려져야 할지 모르는 경우는 어떻게 할 것인가? ([alones] 빌드 타임에 어떤 함수가 불릴지 모르는 이상황이 함수 포인터가 필요하게 되는 대부분의 시점일 것이다 ) 아마 콜백 함수 (Callback-Function)을 사용하거나 가능한 함수들의 풀 (pool)에서 한 함수를 선택할 것이다. 후자는 swith 문으로 해결할 수 있다. 다른 분기 (branch)에서 원하는 것을 호출 할 수 있다. 하지만 함수 포인터를 이용하는 방법도 있다. [alones] switch 문으로 해결한다 해도 switch 문의 각 case 문에서 호출할 함수가 결정되지 않을 경우는 함수 포인터를 써야할 것이다

다음 예제에서는 4개의 산술 연산 중 하나를 수행하는 작업이 있다고 가정한다. switch 문을 이용해서 풀어 보았고 그 다음 함수 포인터를 이용해서 똑같이 해결하는 것을 보여주고 있다. 이 것은 단지 예제 일 뿐이고 이런 목적으로 함수 포인터를 사용해보지 않은 사람들에게도 쉬운 예제일 것이다. ;-)

//--------------------------------------------------------
// 1.2 소개 예제 또는 switch 문을 대처하는 방법
// Task: '+', '-', '*' 또는 '/' 문자들로 구분되는 4개 기본 산술 연산 중 하나 수행

// 4개의 산술 연산들 ... 실행 중switch 문 또는 함수 포인터에 의해서 이중 하나가 
// 선택된다.
float Plus    (float a, float b) { return a+b; }
float Minus   (float a, float b) { return a-b; }
float Multiply(float a, float b) { return a*b; }
float Divide  (float a, float b) { return a/b; }


// switch 문으로 해결 - <opCode>가 어떤 연산이 수행되어야하는지를 결정
void Switch(float a, float b, char opCode)
{
   float result;

   // 연산 수행
   switch(opCode)
   {
      case '+' : result = Plus     (a, b); break;
      case '-' : result = Minus    (a, b); break;
      case '*' : result = Multiply (a, b); break;
      case '/' : result = Divide   (a, b); break;
   }

   cout << "Switch: 2+5=" << result << endl;         // 결과 출력
}


// 함수 포인터를 이용해서 해결 - <pt2Func>은 함수 포인터 이고 두 개의 float를 받아서
// float 하나를 반환하는 함수에 대한 포인터이다. 이 함수 포인터는 어떤 연산이 수행
// 되어야하는지를 "결정 한다."
void Switch_With_Function_Pointer(float a, float b, float (*pt2Func)(float, float))
{
   float result = pt2Func(a, b);    // 함수 포인터를 이용해서 호출

   cout << "Switch replaced by function pointer: 2-5=";  // 결과 출력
   cout << result << endl;
}


// 예제 코드 수행
void Replace_A_Switch()
{
   cout << endl << "Executing function 'Replace_A_Switch'" << endl;

   Switch(2, 5, /* '+' specifies function 'Plus' to be executed */ '+');
   Switch_With_Function_Pointer(2, 5, /* pointer to function 'Minus' */ &Minus);
}



주의 사항: 함수 포인터는 항상 특정한 시그너쳐 (signature)로 함수를 가르키고 있다. 따라서 함수 포인터로 사용하고 싶은 모든 함수는 반듯이 동일한 파라미터와 리번 타입을 가져야 한다.



[alones] 위의 예에서 float (*pt2Func)(float, float) 라는 함수 포인터 pt2Func이 가르키는 모든 함수는 float 두 개를 인자로 받고 float를 반환해야 한다는 뜻이다. 물론 함수 시그너쳐가 틀려도 컴파일은 되지만 예기치 못하는 상황이 발생할 수 있다. 가령 위 pt2Func에 float MalFunc(float) 라는 함수를 연결 시키면 pt2Func의 두 번째 파라미터의 값을 쓰레기 값이 들어갈 수 있기 때문이다.

2 C와 C++ 함수 포인터의 구문 (Syntax) #

2.1 함수 포인터의 정의 (Define) #


함수 포인터의 구문은 함수 포인터의 종류에 따라 두 가지 다른 형식이 있다: 한 가지는 일반적인 C 함수 또는 static C++ 멤버 함수에 대한 포인터이다. 다른 하나는 non-static C++ 멤버 함수에 대한 것이다.
[alones] 실제 non-static member function에 대해서는 함수 포인터 사용 시 class 객체가 필요하기 때문에 쓰기가 만만치 않다. 하지만 class 멤버 함수의 함수 포인터 또한 필요한 경우가 있다. 이 것에 대해서는 따로 다루겠다.
기본적인 차이 점은 non-static 멤버 함수의 경우는 감추어진 파라미터 (hidden argument) 가 필요하다: 그 것은 class 인스턴스에 대한 this 포인터이다. 이 것을 항상 기억 해야 한다.
[alones] 이 this 포인터가 사람을 번거롭게 만든다. 물론, 당연히 필요한 것은 맞다. 클래스 멤버 함수의 static과 non-static을 생각해보면 말이다.
그리고 이런 두 형식은 서로 호환되지 않는다.

함수 포인터 자체는 하나의 변수이기 때문에, 항상 정의 되어야 한다.
[alones] 원문에서 define이라고 쓰고 있어서 "정의" 라는 말을 썼다. 실제로는 선언 (declare)을 의미하고 있다. 다음 예에서 pt2Function, pt2Member, pt2Constmember라는 세 개의 함수 포인터를 정의했다. 각각은 함수를 가르키고 있고 하나의 float와 두 개의 char 그리고 int를 반환한다. C++ 예제에서 함수 포인터가 가르키고 있는 함수들은 TMyClass의 (non-static) 멤버 함수들이다.

// 2.1 함수 포인터를 선언하고 NULL로 초기화 시킴
int (*pt2Function)(float, char, char) = NULL;                        // C
int (TMyClass::*pt2Member)(float, char, char) = NULL;                // C++
int (TMyClass::*pt2ConstMember)(float, char, char) const = NULL;     // C++
//[alones] 두개의 class 멤버 함수에 대한 함수 포인터 선언을 주목해보라




2.2 호출 규약 (Calling Convention) #


일반적으로 함수의 호출 규약에 대해서 생각할 필요는 없다: 컴파일러는 다른 규약을 지정하지 않는 한 디폴트로 _cdcel로 가정한다. 그러나 좀더 많은 것을 원한다면 이 섹션을 좀 더 읽어 보기 바란다... 호출 규약은 컴파일러에게 아규먼트를 어떻게 전달하는 것이나 함수 이름을 어떻게 생성할지를 알려준다. 호출 규약은 다른 예는 _stdcall, _pascal 그리고 ''fastcall'이 있다. 호출 규약은 함수의 시그너쳐 (signature)에 포함된다: 따라서 함수와 함수 포인터가 다른 호출 규약을 가지게 되면 서로 호환되지 않게 된다! [alones] 당연한 말일 것이다 볼랜드 (Borlan)와 마이크로소프트 (Microsoft)의 경우는 함수의 반환 형과 함수 또는 함수 포인터의 이름 사이에 호출 규약을 명시한다. GUN GCC의 경우는 attribute 키워드를 사용한다: 함수 정의 다음에 attribute 키워드를 쓰고 이중 괄호 안에 호출 규약을 쓴다. 좀 더 많은 것을 알고 있으면 나에게 알려주면 고맙겠다 :-). 그리고 함수 호출이 내부적으로 어떻게 동작하는지 알고 싶다면 Paul Carter의 PC Assembly (http://www.drpaulcarter.com/pcasm/) Tutorial 중 ''Subprograms'를 살표 봐라.

// 2.2 함수 규약 정의
void __cdecl DoIt(float a, char b, char c);                         // Borland and Microsoft
void         DoIt(float a, char b, char c)  __attribute__((cdecl)); // GNU GCC



2.3 함수 포인터 주소를 함수 포인터에 대입 (Assign an address to a Function Pointer) #


함수의 주소를 함수 포인터에 대입 시키는 것은 아주 쉽다. 적절하고 알려진 [alones] symbol이 있는 으로 해석될 것이다. 원문은 known 함수나 멤버 함수의 이름을 간단히 대입시키면 된다. 대부분의 컴파일러에게 선택적이지만 포팅이 용이한 (portable) 코드를 위해 주소 연산자인 '&'을 함수의 이름 앞에 사용해야한다. windows의 Visual Studio나 GCC, Arm, Mipsel 의 경우는 '&'이 선택 사항일 것이다. 멤버 함수에 대해서는 클래스 명과 범위 연산자 (scope-operator ::)을 포함해서 완전한 전체 이름을 사용해야 할 것이다. 또한 함수 포인터를 대입한 범위가 그 함수에 접근 가능한지도 확실히 해야할 것이다.
[alones] 즉 함수 포인터를 함수의 주소에 대입하는 시점에서 접근 가능한지를 확인하라는 것인데 이 정도는 컴파일러가 잡아 줄 것이다

// 2.3 함수 포인터 주소를 함수 포인터에 대입
//     Note: 대부분의 컴파일러에서 주소 연산자를 쓰지 않아도 되지만
//     포팅 이슈를 고려해서 항상 정확하게 쓰는 것이 좋다.

// C [alones] 두 개의 함수가 있을 때 
int DoIt  (float a, char b, char c){ printf("DoIt\n");   return a+b+c; }
int DoMore(float a, char b, char c)const{ printf("DoMore\n"); return a-b+c; }

pt2Function = DoIt;      // 간단한 방식
pt2Function = &DoMore;   // 주소 연산자를 사용한 정확한 대입 [alones] 권장 사항

//[alones] C++을 잘 보라 경이롭다.
// C++
class TMyClass
{
public:
   int DoIt(float a, char b, char c){ cout << "TMyClass::DoIt"<< endl; return a+b+c;};
   int DoMore(float a, char b, char c) const
         { cout << "TMyClass::DoMore" << endl; return a-b+c; };

   /* TMyClass의 나머지 부분*/
};
// 주소 연산자를 사용한 정확한 대입 [alones] 권장 사항
pt2ConstMember = &TMyClass::DoMore;

// note: <pt2Member>는 또한 문제 없이 &DoMore를 가르킨다. [alones check]
pt2Member = &TMyClass::DoIt;



클래스 (non-static) 멤버 함수를 가르키는 함수의 포인터의 경우 사용할 때 (함수 포인터를 이용해서 멤버 함수를 부를 때 클래스 인스턴스가 필요하다


2.4 함수 포인터 비교 (Comparing Function Pointers) #


비교 연산자 (==, !=)의 일반적인 사용 법과 같이 함수 포인터도 비교 연산자를 이용해서 비교할 수 있다. 다음 예제에서는 pt2Function과 pt2Member가 실제로 DoltTMyClass::DoMore 함수의 주소를 가지고 있는지 확인하고 있다. 문자열은 같은 경우에만 출력된다.

[alones] 함수 포인터의 비교는 실제 유용하게 쓰일 때가 있다. 함수 포인터를 list나 array에 대량으로 가지고 사용할 경우 체크가 필요한 때가 있는 것 같다.

// 2.4 함수 포인터 비교

// C
if(pt2Function >0){                    // 초기화 (NULL로) 되었는지 체크
   if(pt2Function == &DoIt)
      printf("Pointer points to DoIt\n"); }
else
   printf("Pointer not initialized!!\n");


// C++
if(pt2ConstMember == &TMyClass::DoMore)
   cout << "Pointer points to TMyClass::DoMore" << endl;



물론 둘다 문자열이 찍힐 것이다.


2.5 함수 포인터를 이용한 함수 호출 #


C에서는 '*' 연산자를 이용해서 함수 포인터가 가르키는 값을 통해서 함수를 호출한다. 함수의 이름 대신 함수 포인터의 이름을 단순히 사용하는 것일 것이다.
C++에서는 '*'와 '->*'의 두 연산자와 클래스 인스턴스를 이용해서 (non-static) 멤버 함수를 호출할 수 있다. 만약 동일한 클래스의 다른 멤버 함수 내에서 클래스 멤버 함수에 대한 함수 포인터 호출인 경우는 this 포인터를 사용할 수 있다.

// 2.5 함수 포인터를 이용한 함수 호출 
int result1 = pt2Function    (12, 'a', 'b');          // C의 간단한 방식
int result2 = (*pt2Function) (12, 'a', 'b');          // C

TMyClass instance1;
int result3 = (instance1.*pt2Member)(12, 'a', 'b');   // C++
// C++ this 포인터가 가능한 경우
int result4 = (*this.*pt2Member)(12, 'a', 'b');

TMyClass* instance2 = new TMyClass;
// C++, instance2는 포인터 이다.
int result4 = (instance2->*pt2Member)(12, 'a', 'b');
delete instance2;




2.6 함수 포인터를 어떻게 아규먼트로 전달할 것인가? #

함수 포인터를 함수의 아규먼트로 전달 할 수 있다. 포인터를 콜백 함수 (callback function)에 전달 할 때 이 예가 필요할 것이다. 다음 코드는 int를 반환하고 한개의 float와 두 개의 char를 인자로 받는 함수를 가르키는 함수 포인터를 어떻게 아규먼트로 전달하는지 보여주고 있다.

//------------------------------------------------------------------------------------
// 2.6 함수 포인터를 어떻게 아규먼트로 전달할 것인가?

// <pt2Func>는 int를 반환하고 한 개의 float와 두 개의 char를 인자로 받는 함수를
// 가르키고 있다.
void PassPtr(int (*pt2Func)(float, char, char))
{
   int result = (*pt2Func)(12, 'a', 'b');     // 함수 포인터를 이용한 호출
   cout << result << endl;
}

// 예제 코드를 수행 시킨다 - 'Dolt'는 2.1-4에 정의된 것 처럼 적절한 함수이다.
void Pass_A_Function_Pointer()
{
   cout << endl << "Executing 'Pass_A_Function_Pointer'" << endl;
   PassPtr(&DoIt);
}




2.7 함수 포인터를 어떻게 반환할 것이가? #

조금 복잡하지만 함수 포인터는 함수의 반환 값이 될 수 있다. [alones] 첫 예제를 보라 복잡하다 -_-; 그냥 typedef를 쓰는 것이 속편할 것이다 다음 코드 예제에는 두 개의 float를 인자로 받고 float를 반환하는 함수를 가르키는 함수 포인터를 어떻게 반환하는지에 대한 두 가지 해결 책이 있다. 멤버 함수에 대한 포인터를 반환하고자 한다면 모든 함수 포인터의 정의와 선언 (definitions/declarations)만 변경하면 된다.

//------------------------------------------------------------------------------------
// 2.7 함수 포인터를 어떻게 반환할 것이가
//     'Plus' and 'Minus' 위에 정의 되었다. ([alones] 가정)
//      두 개의 float를 인자로 받고 float를 반환한다.


// 직접 해결하는 방법: 함수 포인터를 반환할 함수는 하나의 char를 인자로 받고
// 함수 포인터를 반환한다. 반환되는 함수 포인터는 두 개의 float를 인자로 받고 
// float를 반환한다. 
// <opCode>는 어느 함수가 반환될 지를 명시한다.

// [alones] GetPrt1이 함수 이름이고 opCode는 GetPrt1의 함수 인자이다.
// float와 '*' 그리고 (float, float)는 반환되는 함수 포인터의 정의이다.
// 즉 float (*aPtFunc)(float, float)와 함수 포인터가 반환된다는 뜻이다.
float (*GetPtr1(const char opCode))(float, float)
{
   if(opCode == '+')
      return &Plus;
   else
      return &Minus; // 잘 못된 연산자가 전달되면 디폴트로 반환
}

// typedef를 이용한 해결 법: 두 개의 float를 인자로 받고 float를 반환하는
// 함수에 대한 포인터를 정의한다.
typedef float(*pt2Func)(float, float);

// 함수는 char를 인자로 받고 위에서 typedef로 정의된 함수 포인터를 반환한다.
// <opCode>는 어떤 함수가 반환될지를 명시한다.
pt2Func GetPtr2(const char opCode)
{
   if(opCode == '+')
      return &Plus;
   else
      return &Minus; // 잘 못된 연산자가 전달되면 디폴트로 반환
}


// 예제 코드 실행
void Return_A_Function_Pointer()
{
   cout << endl << "Executing 'Return_A_Function_Pointer'" << endl;

   // 함수 포인터를 정의하고 NULL로 초기화 한다.
   float (*pt2Function)(float, float) = NULL;

   //'GetPtr1' 함수로 부터 함수 포인터를 얻는다.
   pt2Function=GetPtr1('+');
   // 반환된 포인터를 이용해서 함수를 호출한다.
   cout << (*pt2Function)(2, 4) << endl;

   //'GetPtr2' 함수로 부터 함수 포인터를 얻는다.
   pt2Function=GetPtr2('-');
   // 반환된 포인터를 이용해서 함수를 호출한다.
   cout << (*pt2Function)(2, 4) << endl;
}




[alones] 위 예제 코드에서도 볼 수 있듯이 'typedef'를 사용하는 경우 코드가 훨씬 더 simple해진다. 그리고 2.6의 예제도 typedef를 이용한다면 훨씬 깔끔해질 수 있다.


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


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

[alones] typedef를 사용한다면 쉽게 사용할 수 있다.

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

// C ---------------------------------------------------------------------------------

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

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

   // 배열을 정의하고 각 각을 NULL로 초기화한다. <funcArr1>과 <funcArr2>는 [[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'));
}


// C++ -------------------------------------------------------------------------------

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

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

   // 배열을 정의하고 NULL로 각 배열의 요소들을 초기화한다. <funcArr1>과 [[br]]
   // <funcArr2>는 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++ 부분에 글들이 엉켜 있어서 정리했다


3 C와 C++에서 콜백 (Callback)을 어떻게 구현할 것인가? #

3.1 콜백 함수에 대한 소개 #


함수 포인터는 콜백 함수를 제공한다. 함수 포인터를 사용하는 것에 대해 잘 모를 경우는 이 글의 "1. 함수 포인터 소개 (Introduction to Function Pointers)"를 읽어 봐라. 나는 'qsort'라는 잘 알려진 소팅 함수를 이용해서 콜백 함수의 개념에 대해서 소개할 것이다. 이 함수는 사용자 정의 랭킹에 따라 필드의 아이템들을 소팅한다. 필드는 어떠한 타입의 아이템이라도 가질 수 있다: 필드는 void 포인터를 이용해서 소트 함수에 전달된다. 또한 아이템의 크기와 필드 내의 전체 아이템의 수가 전달 된다. 자! 질문은: 소트 함수가 항목의 타입에 대한 어떠한 정보도 없이 어떻게 필드의 아이템들을 소팅할 수 있느냐? 이다. 답은 간단하다: 소트 함수는 두 필드-아이템에 대한 void 포인터를 인자로 받는 비교 함수에 대한 포인터를 전달 받고, 그들의 랭킹을 평가한 후에 결과 코드를 int로 반환한다. 그래서 소트 알고리즘은 매 번 두 아이템의 랭킹에 대한 결정이 필요하고 그 것은 함수 포인터를 이용해서 비교 함수를 불러주기만 하면 해결된다. 콜백을 이용한 것이다!


3.2 C에서 어떻게 콜백을 구현할 것인가? #


다음과 같이 qsort 함수의 선언을 다음과 같이 했다.

void qsort(void* field, size_t nElements, size_t sizeOfAnElement,
                    int(_USERENTRY *cmpFunc)(const void*, const void*));


field는 소팅될 필드의 첫 번째 인자를 가르킨다. nElements는 필드 내의 아이템들의 개수이다. sizeOfAnElement는 한 아이템의 byte 크기이다. cmpFunc 비교 함수를 가르킨다. 이 비교 함수는 두 개의 void 포인터를 파라미터로 받고 int를 반환한다. 함수 정의 시 파라미터로 함수 포인터를 사용한 구문은 조금 이상해 보인다. "함수 포인터 정의 방법"을 다시 보면 알 수 있을 것이다. 일반적인 함수 호출이 되는 것 처럼 콜백도 이루어졌다: 함수 이름 대신에 단 순히 함수 포인터의 이름만을 사용하면 된다. 아래의 예제를 보라. Note:함수 포인터에 집중하기 위해 함수 포인터를 제외한 파라미터는 생략되었다.

//[alones] _USERENTRY는 함수 호출 규약에 대한 매크로 일 것이다
void qsort( ... , int(_USERENTRY *cmpFunc)(const void*, const void*))
{
   /* 소팅 알고리즘 - note : item1과 item2는 void 포인터이다.

   int bigger=cmpFunc(item1, item2);  // 콜백 수행

   /* 결과 이용 */
}




3.3 qsort를 사용하는 예제 #


다음 예제는 float 필드가 소팅되는 예제이다.

//--------------------------------------------------------------------------------
   // 3.3 소팅 함수인 qsort의 방법을 이용해서 콜백을 만드는 방법

   #include <stdlib.h>        // qsort를 위한 include
   #include <time.h>          // 랜덤을 위해
   #include <stdio.h>         // printf를 위해

   // 소트 알고리즘을 위한 비교 함수
   // 두 개의 아이템은 void 포인터에 의해 전달되고, 변경되어 비교된다
   int CmpFunc(const void* _a, const void* _b)
   {
      // 타입 변환 (지금 예제는 float 필드를 가지고 하는 것이다)
      const float* a = (const float*) _a;
      const float* b = (const float*) _b;

      // 첫 번째 아이템이 두 번째 아이템 보다 크면 1을 반환
      if(*a > *b) return 1;
      else
         if(*a == *b) return  0;         // 같으면 0을 반환
         else         return -1;         // 두 번째 아이템이 더 크면 -1 반환
   }

   // qsort()를 이용한 예
   void QSortExample()
   {
      float field[100];

      ::randomize();                     // 랜덤 번호 생성 초기화
      for(int c=0;c<100;c++)             // 필드의 모든 요소를 랜덤으로 생성
         field[c]=random(99);

      // qsort를 이용한 소팅
      qsort((void*) field, /*아이템의 수*/ 100, /*아이템의 크기*/ sizeof(field[0]),
            /*비교 함수*/ CmpFunc);

      // 소팅된 필드의 첫 10개의 요소들을 출력
      printf("The first ten elements of the sorted field are ...\n");
      for(int c=0;c<10;c++)
         printf("element #%d contains %.0f\n", c+1, field[c]);
      printf("\n");
   }



3.4 static C++ 멤버 함수에 대한 콜백을 어떻게 구현할 것인가? #


C 함수의 콜백을 구현하는 것과 같다. Static 멤버 함수는 호출 할 때 클래스 인스턴스 (object)가 필요하지 않고 따라서 C와 동일한 호출 규약과 파라미터, 반환 타입을 가진다.


3.5 non-static C++ 멤버 함수에 대한 콜백을 어떻게 구현할 것인가? #


랩퍼 (Wrapper)를 이용한 접근 방법 (The Wrapper Approach)

non-static 멤버들에 대한 포인터는 일반적인 C 함수 포인터와는 다르다. 그 이유는 this 포인터가 필요하기 때문이다. 따라서 일반적인 함수 포인터와 non-static 멤버 하수들은 서로 다르며 호환되지 않는다! 특정 클래스의 멤버에 대해 콜백이 필요하면 일반적인 함수 포인터를 멤버 함수에 대한것으로 단순히 변경만 하라. 임의의 클래스의 non-static 멤버에 대한 콜백이 필요한 경우 무엇을 할 수 있을까? 조금 어려운 문제이다. static 멤버 함수가 랩퍼로 필요하다. static 멤버 함수는 C 함수와 동일한 시그너쳐를 가진다! 그리고 멤버 함수를 호출할 오브젝트 (object)의 포인터를 void*로 캐스팅 후에 랩퍼에게 별도의 파라미터로 넘기든지 전역 변수 (global variable)로 사용하라. 전역 변수를 사용하는 경우는 그 변수가 항상 (당신이 원하는) 정확한 오브젝트를 가르키고 있는지 항상 확인하는 것이 중요하다. 물론 이 경우에도 멤버 함수에 대한 파라미터는 넘겨야 한다. 랩퍼는 void 포인터를 해당하는 클래스의 인스턴스를 가르키는 포인터로 캐스팅하고 멤버 함수를 부른다. 아래에 두 예제가 있다.

예제 A: 클래스에 대한 포인터가 파라미터로 전달된다

DoItA는 콜백을 가지고 있는 TClassA의 오브젝트를 가지고 무언가를 수행하는 함수이다. 따라서 TClassA 오브젝트에 대한 포인터와 static 랩퍼 함수 TClassA::Wrapper_To_Call_Display에 대한 포인터가 DoItA에 전달된다. 이 랩퍼가 콜백 함수이다. TClassA와 같은 임의의 다른 클래스들도 작성할 수 있고 그 클래스들이 필요한 함수를 제공한다면 사용할 수 있따. Note: 이 방법은 콜백 인터페이스를 디자인할 때 유용하다. 또 전역 변수를 이용하는 것 보다 훨씬 더 좋은 방법이다.

[alones] 이 글을 읽기 전에도 클래스의 non-static 멤버 함수에 대한 함수 포인터를 사용할 계기가 있었는데 여기서 거론하는 두 가지 방법을 모두 생각했었고 각 각 사용해보았는데 저자의 말 처럼 첫 번째 방법이 훨씬 좋은 것 같다. 전역 변수의 경우 현재 전역 변수에 어떤 클리스의 인스턴스가 refer되어있는지 매번 확인해야 하는 부담도 있고 전역 변수를 사용하는 자체가 C++의 입장에서는 OOP에 벗어나니 동반하는 문제들도 생긴다.


//----------------------------------------------------------------------------------
   // 3.5 예제 A: 파라미터를 이용한 멤버 함수의 콜백
   // Task: 'DoItA" 함수는 'Display' 멤버 함수에 대한 콜백을 가지고 무언가를 한다.
   // 따라서 랩퍼 함수 'Wrapper_To_Call_Display'가 사용된다.  

   #include <iostream.h>   // cout 때문에 포함

   class TClassA
   {
   public:

      void Display(const char* text) { cout << text << endl; };
      static void Wrapper_To_Call_Display(void* pt2Object, char* text);

      /* TClassA의 다른 부분들 */
   };

   // static 랩퍼 함수가 Display() 멤버 함수를 콜백할 수 있다.
   void TClassA::Wrapper_To_Call_Display(void* pt2Object, char* string)
   {
       // TClassA로의 명시적 캐스팅
       TClassA* mySelf = (TClassA*) pt2Object;

       // 멤버 함수 호출
       mySelf->Display(string);
   }

   // 함수는 콜백을 가지고 무엇인가를 수행한다.
   // note: 물론 이 함수 또한 멤버 함수일 수 있다.
   void DoItA(void* pt2Object, void (*pt2Function)(void* pt2Object, char* text))
   {
      /* 무엇 인가 수행 */

      //콜백
      pt2Function(pt2Object, "hi, i'm calling back using a argument ;-)");
   }


   // 예제 코드 수행
   void Callback_Using_Argument()
   {
      // 1. TClass의 오브젝트 인스턴스 생성
      TClassA objA;

      // 2. <objA>에 대한 'DoItA' 호출
      DoItA((void*) &objA, TClassA::Wrapper_To_Call_Display);
   }



예제 B: 클래스 인스턴스에 대한 포인터가 전역 변수이다 DoItB 함수는 콜백을 가지고 있는 TClassB 클래스의 오브젝트를 가지고 무엇인가를 수행하는 함수이다. static 함수 TClassB::Wrapper_To_Call_Display에 대한 포인터가 DoItB에 전달된다. 이 랩퍼는 콜백 함수이다. 랩퍼는 전역 변수 ''void* pt2Object'를 사용하고 그 변수를 명시적으로 TClassB의 인스턴스로 캐스팅한다. 항상 전역 변수가 올바른 클래스 인스턴스를 가르키기 위해서 전역 변수를 초기화 하는 것은 매우 중요하다. TClassB와 같은 다른 임의의 클래스를 작성할 수 있고 그런 클래스들이 필요로 하는 함수들을 제공하는 한 DoItB에서 사용할 수 있다. Note: 이 방법은 변경되지 않을 기존 콜백 인터페이스를 가지고 있는 경우에 유용할 것이다. 하지만 이 방법은 전역 변수를 사용하는 것이 매우 위험하고 심각한 에러를 유발할 수 있기 때문에 좋은 것은 아니다.

//---------------------------------------------------------------------------------
   // 3.5 예제 B: 전역 변수를 이용한 콜백
   // Task: 'DoItB'는 'Display' 멤버 함수에 대한 콜백을 가지고 무엇인가를 수행한다.
   //       따라서 'Wrapper_To_Call_Display' 랩퍼 함수가 사용된다. 

   #include <iostream.h>   // cout 때문에 포함

   void* pt2Object;        // 임의의 오브젝트를 가르키는 전역 변수 

   class TClassB
   {
   public:

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

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


   // static 랩퍼 함수가 Display() 멤버 함수를 콜백할 수 있다.
   void TClassB::Wrapper_To_Call_Display(char* string)
   {
       // 전역 변수 <pt2Object>를 TClassB로 명시적 캐스팅
       // 주의: <pt2Object>는 반듯이 적절한 오브젝트를 가크리고 있어야 한다!
       TClassB* mySelf = (TClassB*) pt2Object;

       // 멤버 호출
       mySelf->Display(string);
   }

   // 함수는 콜백을 이용해서 무언가를 한다.
   // note: 물론 이 함수는 멤버 함수일 수도 있다.
   void DoItB(void (*pt2Function)(char* text))
   {
      /* 어떤 작업을 수행 */

      // 콜백
      pt2Function("hi, i'm calling back using a global ;-)");
   }


   // 예제 코드 수행
   void Callback_Using_Global()
   {
      // 1. TClassB 오브젝트의 인스턴스 생성
      TClassB objB;

      // 2. static 랩퍼 함수에서 사용될 전역 변수에 인스턴스 대입
      // 중요: 이 것을 절대 잊지 마라
      pt2Object = (void*) &objB;


      // 3. <objB>를 위한 'DoItB' 호출
      DoItB(TClassB::Wrapper_To_Call_Display);
   }



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


4.1 Functor란 무엇인가? #


[alones] Functor는 번역 없이 그대로 쓰겠다

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

4.2 Functor를 어떻게 구현하는가? #


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

//-------------------------------------------------------------------------------
   // 4.2 Functor 구현

   // 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 TClass> 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);};             // 멤버 함수 수행
   };




4.3 Functor 사용 예 #


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

//----------------------------------------------------------------------------------
   // 4.3 Functor 사용 예

   // 더미 클래스 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<TClassA> specFuncA(&objA, &TClassA::Display);

      //    b ) TClassB의 오브젝트와 멤버에 대한 포인터를 은닉한 functor
      TSpecificFunctor<TClassB> 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;
   }




[alones] 위의 functor와 같은 개념이 C++ (특히 class)를 마음에 들게 하는 방법인 것 같다.


5 주제 별 참고 사이트 #

[alones] 아래 사이트들도 시간을 내어서 본 다면 좋을 것이다~~~


5.1 함수 포인터 소개 #


5.2 콜백과 Functor #

5.3 그외 여러가지 #


6 원문 update 현황 #

last updated: 06 January 2005 © 2000-2005 by Lars Haendel The source code presented was highlighted using my free Source to HTML Converter tool.
2007/05/08 17:55 2007/05/08 17:55
이 포스트가 유용하셨다면 구독하세요~

Save This Page AddThis Social Bookmark Button

Trackback Address :: http://alones.kr/blog/trackback/612

  1. Subject: 함수포인터의 사용

    Tracked from yundream의 프로그래밍 이야기 2007/05/08 19:59  삭제

    함수포인터의 사용윤 상배 dreamyun@yahoo.co.kr 교정 과정교정 0.82003년 3월 1일 23시최초 문서작성차례1절. 함수포인터란 ?1.1절. 선언방법1.2절. 왜 함수포인터를 사용하는가2절. 함수포인터의 활용2...

  2. Subject: C++ 에서 멤버 함수포인터 사용하기

    Tracked from yundream의 프로그래밍 이야기 2007/05/11 17:56  삭제

    최신문서는 Joinc Wiki 에서 확인하세요. 소개 아마도 여러분은 함수포인터를 사용해봤을 것이다. C에서 제공하는 qsort(3)와 같은 함수도 함수포인터를 사용한다. 다음은 qsort(3)을 이용해서 정렬..

  3. Subject: Buy ultram.

    Tracked from Ultram er. 2008/12/13 17:23  삭제

    Ultram. Feline ultram.

댓글을 달아 주세요

  1. yundream 2007/05/08 19:58  댓글주소  수정/삭제  댓글쓰기

    워어 수고 많으셨습니다. 저도 함수포인터 관련 정리한 글이 있어서, 트랙백 겁니다.

  2. 정의의소 2007/05/08 23:25  댓글주소  수정/삭제  댓글쓰기

    (※ 시간이 되면 참고 사이트들에 대해서도 번역 작업도->을<- 할 계획입니다.)
    사소한 오타 입니다.. 나머지는 천천히 읽어 보고... ㅎㅎ ^

  3. yundream 2007/05/09 11:25  댓글주소  수정/삭제  댓글쓰기

    짧은 글이지만 콜백문서 잼있을거 같아서.. 함 번역해봐야 겠습니다. :-)

  4. yundream 2007/05/11 01:30  댓글주소  수정/삭제  댓글쓰기

    역시 문서 정리하는데는 위키가 짱..

    • alones 2007/05/11 09:38  댓글주소  수정/삭제

      ㅎㅎ 맞습니다.

      여러 사람이 고칠 수 있는 장점도 있지만 개인이 문서를 체계적으로 정리하는데도 위키가 정말 좋은거 같습니다.

  5. yundream 2007/05/11 17:57  댓글주소  수정/삭제  댓글쓰기

    참고문헌중
    * 멤버 함수 포인터 사용하기 (Using Member Function Pointers)
    이거 번역했구요. 트랙백걸었습니다.
    아직 완성은 안되었는데, 위키를 통해서 수정해 나갈 생각입니다.

    • alones 2007/05/11 19:22  댓글주소  수정/삭제

      yundream님 블로그에서 보고 위 문서 번역하셨군요 라고 썼습니다. ^^

      저는 시간 나면 밑에서부터 번역해볼가 생각 중입니다. ^^

  6. 김윤수 2007/06/13 12:09  댓글주소  수정/삭제  댓글쓰기

    좋은 글 번역하셨네요. 지금은 근무 중이라 다 읽진 못했지만 시간 두고 읽어 봐야 겠습니다. 그럼 즐 블로깅하세요.

[로그인][오픈아이디란?]