2015.09.02 07:08


1. 함수의 원형

본격적으로 내용을 다루기에 앞서 함수 포인터의 원형과 포인터의 대상이 될 함수와의 원형은 같아야 한다.
이 점은 일반 서적에서 다루고 있는 내용이라 다 알 것이라고 생각되므로 함수의 원형에 대하여 간단하게
Q/A 식으로 짚고 넘어가겠다. 이해가 안되면 무조건 외우는 것도 한가지 방법이라고 생각됨.
마지막으로 함수 포인터는 함수가 아니라 원하는 함수의 주소를 담고 있는 변수이다. 근본적으로는 포인터
(변수 포인터)에 다른 변수의 주소를 넣는 것과 별로 다를 것이 없다. 다만 함수 포인터의 경우는 변수의
주소가 아닌 함수의 주소라는 점만 다를 뿐이다.

int a;
char c[5];
float* f;
"Hello, World"

Q) 위의 코드에서 a, c, f,
"Hello, World"의 원형은?
A)
int, char [], float *, const char *

만일 이 문제들을 못맞추었다면 다시 한번 책을 속독하기를 권함.

void foo();


const char* bar(int num);


class MyClass {
public:
   
void Func1(const string& txt);
   
static void Func2(const string& txt);
};

Q) foo, bar, MyClass::Func1, MyClass::Func2의 원형은?
A)
void (*)(), const char* (*)(int), void (MyClass::*)(const string&), void (*)(const string&)


2. 함수 포인터(전역 함수 포인터) / 멤버 함수 포인터

가. 어떻게 정의/선언하고 사용하는가?

    함수 포인터의 대상이 되는 함수의 원형을 적고 괄호안의 * 연산자의 뒤에 원하는 이름을 붙여서 사용.
    (괄호가 있는 이유는 없을 경우 함수의 리턴형이 * 연산자에 의해 원래 리턴형의 포인터로 처리되기
    때문에, 강력한 우선 순위를 가지는 괄호로 함수 포인터임을 명시.)

    예 1)

   
void foo();
   
void (*foo_ptr)();             // 로 정의/선언 후
    foo_ptr = &foo;                
// 로 함수의 주소를 대입 cf) void (*foo_ptr)() = &foo;
    (*foo_ptr)();                  
// 로 사용 cf) foo_ptr(); 도 가능

   
const char* bar(int num);
   
const char* (*bar_ptr)(int);   // 혹은 const char* (*bar_ptr)(int num);로 정의/선언
    bar_ptr = &bar;                
// 호출하려는 함수의 주소를 넣고
    (*bar_ptr)(5);                
// 로 사용 cf) bar_ptr(5); 도 가능

    예 2)

   
class MyClass {
   
public:
       
void Func1(const string& txt);
       
static void Func2(const string& txt);
    };

   
void (MyClass::*Func1Ptr)(const string&) = &MyClass::Func1;
   
void (*Func2Ptr)(const string&) = &MyClass::Func2;        

    MyClass mc;
    (mc->*Func1Ptr)(
"abc");
    (*Func2Ptr)(
"abc");            // 형태로 사용 cf) Func2Ptr("abc"); 도 가능

    예 3)

    만일 MyClass::Func1, MyClass::Func2에 대한 함수 포인터를 MyClass의 멤버 변수로 하려면

   
class MyClass {
   
public:
       
void Func1(const string& txt);
       
static void Func2(const string& txt);
       
void (MyClass::*Func1Ptr)(const string&); // 내부라도 MyClass 스코프임을 밝혀야 함.
       
void (*Func2Ptr)(const string&);
    };

    MyClass mc;
    mc.Func1Ptr = &MyClass::Func1;
// 클래스내에서 setter를 사용하면 그냥 &Func1
    mc.Func2Ptr = &MyClass::Func2;
// 편의상 public으로 지정해서 대입했음.

    (mc.*(mc.Func1Ptr))(
"abc");    // 또는 (mc.*mc.Func1)("abc");
    (*mc.Func2Ptr)(
"abc");         // 전역 함수 포인터와 유사하므로 mc.Func2Ptr("abc") 가능.

    cf) 다른 멤버 함수에서 멤버 함수 포인터를 사용할 때는
    (
this->*Func1Ptr)("abc");
    Func2Ptr(
"abc");               // (*Func2Ptr)("abc")

    물론 꼭 MyClass의 멤버 변수로 존재할 필요는 없다. 다른 클래스의 멤버 변수로도 가능하다.

    주의 : 위의 경우는 꼭 필요한 상황인지, 설계상의 문제는 없는지 다시 한 번 고려할 것.
           다른 방법으로도 충분히 해결되는 문제임.


나.
typedef의 사용법 & 함수 포인터 배열

    함수 포인터의 배열은 무척 간단하다. 위의 예제의 foo 함수의 경우에는

   
void (*foo_ptr[])() = {foo1, foo2, foo3, ...};     // 선언&정의 + 대입
    (*foo_ptr[i])();                                  
// 사용

    로 하면 된다. 다른 함수 포인터도 유사하다.

    위에서 밝힌대로 함수 포인터는 변수이다. 변수는 형을 가진다. 그러면 함수 포인터의 형은 무엇인가?
    호출하려는 함수의 원형이 그 함수 포인터의 형이다. 즉 
typedef를 사용할 수 있다는 말이 된다.
    foo 함수에 대한 함수 포인터의 경우를 예로 들면

   
typedef void (*foo_ptr)();

    형태로 사용한다. 다만
void (*foo_ptr)();는 foo_ptr이 함수 포인터의 이름인데 반해
   
typedef void (*foo_ptr)(); 에서 foo_ptr은 함수 포인터의 형, void (*)()을 뜻하게 된다.
   
int a처럼 선언하듯이 typedef를 쓰면 foo_ptr fp; 뿐만 아니라 배열 선언을 foo_ptr fp[5];
    처럼 쓸 수 있고, 한결 깔끔하고 편리해지게 된다.
    개인적으로 함수 포인터와
typedef는 꼭 같이 쓰기를 권유한다.

    예 1)

   
class MyClass {
   
public:
       
void Func1(const string& txt);
       
static void Func2(const string& txt);
    };

   
typedef void (MyClass::*Func1Ptr)(const string&);
   
typedef void (*Func2Ptr)(const string&);

    Func1Ptr fptr = &MyClass::Func1;      
// 한결 더 의미가 명확해지고 코드 중복을 피할 수 있다.
    Func2Ptr ftpr2 = &MyClass::Func2;

    MyClass mc;
    (mc.*fptr)(
"abc");
    (*fptr2)(
"abc");

    예 2)

   
class MyClass {
       
typedef void (MyClass::*Func1Ptr)(const string&);
       
typedef void (*Func2Ptr)(const string&);
   
public:
       
void Func1(const string& txt);
       
static void Func2(const string& txt);
        MyClass() : fptr(Func1), fptr2(Func2) { }
       
void Invoke(const string& txt)
        {
            (
this->*fptr)(txt);
            (*fptr2)(txt);
        }
   
private:
        Func1Ptr fptr;
        Func2Ptr fptr2;
    };

    MyClass mc;
    mc.Invoke(
"abc");


참고 사이트
http://www.newty.de/fpt/fpt.html#defi

신고


Posted by injunech
2015.09.02 06:50


< 함수 포인터 >

먼저 이 글은 포인터에 대한 이해를 필요로 한다.

포인터에 대한 기본지식이 있다고 가정하고 글을 쓰도록 하겠다.


int GetAreaEx( int x, int y )
{
    return x * y;
}



우선 이런 간단한 함수가 있다. 우리는 이 함수를 호출하기 위해 명시적으로

GetAreaEx( x, y );

이런식으로 기술해야 한다.

하지만 예를 들어 GetArea2, GetArea3, ..., GetAreaN 이런식으로 비슷한 함수가 존재하고

이를 상황에따라 다르게 호출해야 한다면 이 방식으로는 관리도 어려울 뿐더러 효율성도 떨어지고 코드량도 많이질 것이다.

또한 외부(스크립트 등)에서 어떤 특정한 함수를 호출하려 할때도 방법이 묘연할 것이다.


int (*GetArea)( int, int );

이 선언은 무엇일까?

언뜻보기에는 함수를 선언하는 것 같다.

이 선언은 함수에 대한 포인터를 선언한 것이다.

변수의 주소를 담는 포인터와 마찬가지로 함수포인터는 함수의 주소를 담는다.

GetArea = GetAreaEx; // 함수포인터 GetArea에 GetAreaEx()의 주소를 담는다
int nArea = (*GetArea)( x, y ); // (*GetArea)( x, y ); 로 GetAreaEx()함수를 호출하고 리턴받은 값을 nArea에 대입



이런식으로 GetAreaEx를 호출할 수 있다.

유의할점은 *GetArea를 꼭 ()로 감싸주어야 한다는 사실이다.

빼먹으면 컴파일러가 함수포인터를 통한 호출로 인식하지 못한다.


int (*GetArea[])( int, int ) = { GetAreaEx, GetArea2, GetArea3, ..., GetAreaN };

이것은 함수포인터 배열을 정적으로 선언한 것이다. 이렇게 배열로 기능이 비슷한 함수들을 묶어놓았다.

void CallFunc( int nState, int x, y )
{
    int nResult = (*GetArea[nState])( x, y );
}



그리고 그 함수들을 상황에 맞게 호출한다.

만약 함수포인터를 쓰지 않는다면

void CallFunc( int nState, int x, int y )
{
    int nResult;
    switch( nState )
    { 
         case STATE_EX:
              nResult = GetAreaEx( x, y );
         break;
         case STATE_2:
              nResult = GetArea2( x, y );
         break;
         case STATE_3:
              nResult = GetArea3( x, y ); 
         break;
    }
}


위와 같이 기술해야 할 것이다.

두 방식의 차이점과 함수포인터의 이점을 알 수 있겠는가

그렇다면 함수포인터 배열을 동적으로 할당하는 방법은 없을까?

다음과 같은 방법으로 할당할 수 있다.

int (**GetArea)( int, int ); // 함수포인터의 포인터
GetArea = new (int (*[N])( int, int )); // N은 배열의 크기



그리고 다음과 같이 사용하면 된다.

GetArea[0] = GetAreaEx;
GetArea[1] = GetArea2;
GetArea[2] = GetArea3;
...

int nResult = (*GetArea[nState])( x, y );



물론 사용후 delete [] GetArea; 해서 해제하는것을 잊으면 안된다.



< 클래스 멤버함수의 함수포인터화 >

함수포인터는 함수의 주소값을 담는다고 했다.

그렇다면 클래스 멤버함수의 주소값도 단순히 함수포인터에 담아서 호출할 수 있지 않을까?

int (*func)();
func = CFunc::GetArea;


하지만 이 방법은 GetArea()멤버함수가 static으로 선언되었을 때만 가능하다.

static으로 선언되지 않은 멤버함수(멤버변수를 건들여야 하는 멤버함수)를 이 방법으로 담으려 한다면 컴파일 에러가 뜰 것이다.

여기에 다음과 같은 해결방법이 있다.

첫번째 방법은

class CFunc
{
public:
    static int GetArea( CFunc * cls, int x, int y );
};



위와 같이 선언하고 호출할때 해당 인트턴스의 포인터를 넘겨줘서

int GetArea( CFunc * cls, int x, int y )
{
    int a = cls->GetZ();
}



이런식으로 멤버변수를 읽거나 쓸수 있겠지만 이 방식으로는 한계가 있다.

Get, Set 같은 public 외부함수로 억세스하지 않으면 private나 protected안에 선언되어 있는

멤버변수는 건드릴 수 없다.

두번째는 멤버함수의 소속을 명시화하는 방법이다.

int (CFunc::*func)( int, int );
func = CFunc::GetArea;
CFunc A;
(A.*func)( x, y );



위와 같은 방법으로 해결가능하다. 물론 호출할 인스턴스가 명확해야 한다.

세번째는 클래스 안에 함수포인터를 멤버변수로 두고 별도의 함수포인터를 컨트롤하는 멤버함수를 만드는 방법이 있다.

이 방법이 멤버함수 관리가 가장 쉬우며 효율적이다.

class CFunc
{
public:
    int (CFunc::*pFunc)( int, int );
    int GetArea( int x, int y );
    void CallFunc( void ) { (this->*pFunc)( x, y ); } // CallFunc 함수호출시 자체 오버헤드를 줄이기 위해 inline
    CFunc();
    ~CFunc() {}
};

CFunc::CFunc()
{
    pFunc = GetArea;
}
int CFunc::GetArea( int x, int y )
{
    return x * y;
}


위와 같다면 CallFunc(); 로 GetArea 호출이 가능해진다.

지금은 단순히 한개의 멤버함수 호출만 할뿐 의미가 없다. 이제 실제 효율적으로 쓰이게 배열을 써보자.

class CFunc
{
public:
    int (CFunc::**pFunc)( int, int );
    int GetArea( int x, int y );
    void CallFunc( int nState, int x, int y ) { (this->*pFunc[nState])( x, y ); }
    CFunc();
    ~CFunc();
};

CFunc::CFunc()
{
    // init
    pFunc = new (int (CFunc::*[10])( int, int )); // 동적할당, 10에는 원하는 멤버함수 갯수만큼
    // 0번은 남겨둔다.

    pFunc[1] = GetArea;
    pFunc[2] = GetAreaEx;
    pFunc[3] = GetArea2;
    pFunc[4] = GetArea3;
    ...
    pFunc[9] = GetArea9;
}
CFunc::~CFunc()
{
    delete [] pFunc; // 해제
}
int CFunc::GetArea( int x, int y )
{
    return x * y;
}


자, 이제 함수하나의 호출로 상황에따라 여러 멤버함수를 호출할 수 있는 기반이 마련되었다.

CFunc A;
A.CallFunc( nState, x, y );



이렇게...

어떠한가. 함수포인터의 위력이 느껴지는가?




< STL을 이용한 함수포인터 관리 >

우리는 지금까지 함수포인터를 동적으로 배열을 할당해서 써왔다.

함수 포인터를 STL(Standard Template Library)을 써서 관리해보자.

클래스의 멤버함수의 함수포인터화에서 3번째 방법을 조금 개선시켜 보겠다.



단순히 인덱스(숫자)를 이용한 관리라면 deque정도가 괜찮을듯 싶으나,

만약 함수의 이름을 문자열로 호출하고 싶다면 map을 써볼 수 있다.

(만약 FuncCall( "GetArea", x, y ); 이런식으로 멤버함수를 호출하고 싶다면)

map은 내부적으로 트리구조를 가지고 있다.

그래서 따로 정적/동적으로 배열을 할당하지 않아도 입력된 값을 비교해서 스스로 자신의 크기를 늘린다.

mapValue["GetArea"] = 99;

이런식으로 []안에 숫자 뿐만아니라 비교할 수 있는 모든 것이 들어갈 수 있다.

먼저 map을 사용하기 위해

#include < map >
using namespace std;



를 선언한다. map은 표준 네임스페이스를 사용하므로 std의 이름공간을 활용한다.

map< []안에 들어갈 타입, 입력될 데이터타입, 비교 논리 > mapFunctor;

선언방법은 이렇게 되는데 비교 논리는 첫번째 인수가 클래스이고 안에 비교오퍼레이터가 있다면 생략가능하다

자, 이제 해보자.



struct ltstr
{
    bool operator() ( const char * s1, const char * s2 ) const
    {
         return strcmp( s1, s2 ) < 0;
    }
};

class CFunc
{
public:
    typedef int (CFunc::*_Func)( int, int );
    map< const char *, _Func, ltstr > mapFunctor;
    int GetArea( int x, int y );
    void CallFunc( const char * szFuncName, int x, int y ) 
    { 
         (this->*mapFunctor[szFuncName])( x, y ); 
    }

    CFunc();
    ~CFunc();
};

CFunc::CFunc()
{
    // init
    mapFunctor["GetArea"] = GetArea;
    mapFunctor["GetAreaEx"] = GetAreaEx;
}
CFunc::~CFunc()
{
    // map 클리어
    mapFunctor.clear();
}
int CFunc::GetArea( int x, int y )
{
    return x * y;
}


char * 대신 string을 사용한다면 string안에 내부적으로 비교 오퍼레이터함수가 있기 때문에

map< string, _Func > mapFunctor;

이렇게 선언하고 사용할 수 있을 것이다.

이제 A.CallFunc( "GetAreaEx", x, y ); 란 호출로 GetAreaEx를 호출할 수 있다.

이 방식은 여러가지로 응용가능한데 스킬명에 의한 화면효과 호출이라던지

C로 미리 작성된 내부 함수를 외부 스크립트로 호출한다던지 할때 유용하게 쓰일 수 있다.

(스크립트 호출일 경우 함수이름을 인덱스화 해서 deque를 쓰는게 속도상 더 유리할 듯 하다)






출처 : http://zeph.tistory.com/155



신고


Posted by injunech
2015.04.10 22:08


함수포인터


포인터가 무엇인지는 다들 아실텐데요, 특정 변수에 대한 메모리 주소를 담을 수 있는 변수를 포인터 변수라고 합니다. 그렇다면 함수포인터란, 특정 함수에 대한 메모리 주소를 담을 수 있는 것 이라고 정의할 수 있겠습니다.


함수포인터를 쓰는 이유는 무엇일까요?

  1. 프로그램 코드가 간결해집니다.

  2. 함수포인터를 배열에 담아서도 사용할 수 있으므로 중복되는 코드를 줄일 수 있습니다.

  3. 상황에 따라 해당되는 함수를 호출할 수 있으므로 굉장히 유용합니다.

그 외에도 함수 포인터를 이용하여 콜백함수를 구현할 수 있게 되는 등 편리하고 유용한 코드를 작성할 수 있게 됩니다.



우선 함수포인터의 모양에 대해 알아보도록 하겠습니다.

int (*FuncPtr) (intint)


함수포인터는 위와 같은 모양을 띕니다. 함수의 프로토타입 선언과 모양이 비슷하죠?

함수의 프로토타입과 다른점이 있다면 함수 이름앞에 포인터를 가르키는 *이 붙는 다는 것인데요. 이렇게 선언이 되게 되면 FuncPtr 이라는 함수의 주소를 담을수 있는 '변수'가 생기는 것입니다. 

이 FuncPtr 함수포인터가 담을 수 있는 함수는 위와 같은 모양을 띄어야 합니다. 즉, 함수의 리턴형은 int 여야하고, int형 파라미터 2개를 받는 함수여야 하는 것입니다.


예를 들어,

1:  int add (int first, int second)

2: double div (double first, double second)

위의 보이는 두 함수가 있다고 가정할 때, 함수포인터의 선언 모양과 똑같이 생긴 add 라는 함수의 주소만을 담을 수 있는 것입니다. 


아래의 사용 예제를 한 번 더 보시겠습니다.

1
2
3
4
FuncPtr = add    (o)
FuncPtr = &add   (o)
FuncPtr = div    (x)
FuncPtr = add()  (x)


1, 2 :  2가지 방법 모두 괜찮은 사용 방법입니다. 어떤 것을 쓰셔도 무관합니다.
: div는 FuncPtr의 선언 모양과 프로토타입이 달라서 사용할 수 없습니다. 에러가 발생합니다.
4 : add() 처럼 함수를 호출할 때 처럼 쓰는 것은 결과값이 함수의 호출 이후 리턴 값이 되는 것입니다. 따라서 add() 는 int형을 가리키게 되는 것이므로 사용방법 자체가 잘 못 되었습니다. 에러가 발생합니다.



이렇듯 함수포인터는 담고 싶은 함수의 프로토타입을 따라 선언하여 사용하시면 됩니다. 하지만, 이 모양이 복잡하기 때문에 typedef를 이용하여 타입의 모양을 단순화 시키는 작업을 해 줄수도 있습니다.

typedef int (*FuncPtr)(intint)


프로그램 상단에 위와 같이 선언한 후, 실제 사용을 하실 때에는 FuncPtr 이라는 Type으로 새로운 변수를 사용하실 수 있습니다.


1
2
FuncPtr testFP = NULL;
testFP = add;


이렇게 말이죠. 아, 참고로 모든 변수 특히 포인터 변수를 선언해 주실 때, 초기화 해주는 습관은 정말 좋은 습관이십니다 :) 크리티컬 에러를 미리 예방할 수 있는 방법 중 하나입니다.



자 이제 마지막으로, 함수포인터를 이용해서 만든 실제 예제를 한 번 보여드리도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
 
// 함수포인터 타입 정의
typedef int (*calcFuncPtr)(int, int);
 
 
// 덧셈 함수
int plus (int first, int second)
{
    return first + second;
}
// 뺄셈 함수
int minus (int first, int second)
{
    return first - second;
}
// 곱셈 함수
int multiple (int first, int second)
{
    return first * second;
}
// 나눗셈 함수
int division (int first, int second)
{
    return first / second;
}
 
// 매개변수로 함수포인터를 갖는 calculator 함수
int calculator (int first, int second, calcFuncPtr func)
{
    return func (first, second);     // 함수포인터의 사용
}
 
int main(int argc, char** argv)
{
    calcFuncPtr calc = NULL;
    int a = 0, b = 0;
    char op = 0;
    int result = 0;
     
    scanf ("%d %c %d", &a, &op, &b);
     
    switch (op)    // 함수포인터 calc에 op에 맞는 함수들의 주소를 담음
    {
        case '+' :
            calc = plus;
            break;
 
        case '-':
            calc = minus;
            break;
 
        case '*':
            calc = multiple;
            break;
 
        case '/':
            calc = division;
            break;
    }
     
    result = calculator (a, b, calc);
 
    printf ("result : %d", result);
     
    return 0;
}


실행결과는 다음과 같습니다.


간단한 소스코드라 어려운 점은 없을 겁니다. 혹시 코드에 이해 안가는 부분이 있다면 댓글 남겨주시면 바로 답변 드립니다.



신고


Posted by injunech