본문 바로가기
컴퓨터/C++

c++ 함수포인터 부터 std::function까지

by 김짱쌤 2015. 3. 10.

개념 및 기초

  1. 함수의 시작 번지를 가르키는 포인터 (어셈블리 언어의 호출 스택을 생각해보자)
  2. 포인터 형식 자체가 다르다.
    • 리턴타입(*함수 포인터 이름)(인수목록)
    • 함수 : int Func(int a) => 포인터 : int(*funcPtr)(int)
    • 함수의 시작번지에 들어가기 앞서 인수들의 목록과 리턴 형식(주소) 정보를 (스택에) 저장해야하므로

함수 포인터 사용 방법

  1. 대입 & 호출
    • 대입 : funcPtr = Func; (괄호없이 단독으로 사용된 함수명은 함수의 시작 번지를 나타내는 포인터 상수.)
    • 호출 : 원칙적으로 (*funcPtr)(2); 하지만 컴파일러는 funcPtr(2); 도 동작하게한다.
    • 인자가 잘못 들어가거나 리턴형식을 잘못된 타입으로 받으면 당연히 컴파일 에러, 함수 포인터 형식이 복잡한 이유
  2. 하나의 고유한 타입인 함수 포인터.
    • 원형이 다른 함수 포인터끼리 직접 대입, 복사 불가.
    int(*funcPtr1)(int), double(*funcPtr2)(float);
    funcPtr1 = funcPtr2; //에러
    • 캐스팅 가능
    int(*funcPtr1)(int), double(*funcPtr2)(float);
    funcPtr1 = (int(*)(int))funcPtr2;
  3. 응용
    • 함수 포인터 배열
    int(*funcPtrArr[MAX_FUNC_NUM])(int); //함수 포인터 변수명 다음에 배열 크기 선언
    funcPtrArr[0] = funcPtr1;
    • 함수 포인터의 포인터
    int(**funcPPtr)(int); //일단 이건데 불편, 위에도 마찬가지
    typedef int(*FUNCPTR)(int); //함수 포인터 타입을 typedef로 정의하고(정의 형식 주의)
    FUNCPTR* funcPPtr; //이렇게나
    FUNCPTR funcPtr[MAX_FUNC_NUM]; //이렇게 사용하는 것이 가독성이 높다.

함수 포인터의 의미

  1. 함수를 변수로 사용할 수 있다.
    • 하나의 함수포인터만을 가지고 조건에 따라 다른 함수를 실행시킬 수 있다.
      (fsm에서 활용하는 방법)
    • 함수를 인자로 넘길 수 있다.
    • 함수 포인터 배열이나 구조체를 사용하여 전체 함수그룹을 변경하는 것도 가능하다.
  2. 사용하기 좋은 경우
    • 조건에 따라 선택해야 할 함수가 두 개 이상인 경우. 함수 포인터 배열을 활용하자
    • 함수 선택 분기와 실제 호출 시점이 다른 경우. 미리 선택한뒤 변수에 저장된 함수를 바로 호출하면 된다.
    • 호출할 함수가 외부 모듈(DLL)에 있는 경우, 동적 연결하려면 함수 포인터 사용해야한다.

함수 인자 사용하기

  1. 예제 : 퀵소트
    • 퀵소트 형식
      void qsort(void *base, size_t num, size_t width,
      int ( *compare )(const void *, const void *));
    • compare 함수
    int compare(const void *a, const void *b)
    {
        if (*(int *)a == *(int *)b) return 0;
        if (*(int *)a > *(int *)b) return 1;
        return -1;
    }
    • 실제 퀵소트 호출
    int ar[]={34,25,27,19,4,127,9,629,18,7,9,165};
    qsort(ar,sizeof(ar)/sizeof(ar[0]),sizeof(int),compare);
    for (int i=0;i<sizeof(ar)/sizeof(ar[0]);i++) {
         printf("%d번째 = %d\n",i,ar[i]);
    }

클래스 멤버함수의 함수 포인터

  1. 클래스 내부에 있는 메소드, 멤버함수들을 함수 포인터로 사용하고 싶을때.
  2. 기존 C의 함수들은 정적(Static)함수로, 소속이나 사용 인스턴스와 무관하게 사용되었다.
  3. 하지만 클래스의 멤버함수들은 Class 이름을 네임스페이스로 (Class::Function();)
    인스턴스에 의존적으로(this->Function();) 사용되므로 차이가 있다.
  4. 예전 스터디 static부분참조
  5. 실제 코드로 보는 일반/멤버함수 포인터의 차이점과 사용방법 예시

    class World
    {
    public:
     void Close() {}
     static void Open() {}
    };
    
    void foo(){}
    int main()
    {
       //대입
       void(*fPtr1)() = &foo;            //일반 함수 포인터
       void(*fPtr2)() = &World::Open;    //클래스 멤버 함수 포인터. 
       //클래스의 네임스페이스 설정이 안되어 있으나 static이므로 접근 가능
       void(*fPtr3)() = &World::Close;   //함수 포인터 소속 불명.
       void(World::*fPtr4)() = &World::Close; //함수의 소속을 명시하면 OK
    
       //호출
       fPtr4();                          //하지만 인스턴스에 바인딩 되지 않으면 에러
       World earth; 
       earth.fPtr4();                   //earth 내부의 멤버함수 fptr4를 찾기때문에 불가능
       (earth.*fPtr4)();                //원칙적 사용방법 이렇게 쓰도록 한다.
    }
  6. 결론

    • 멤버함수를 클래스 외부에서 함수 포인터로 선언(사용)할 때 타입에 클래스 소속을 명시해서 써야한다.
    • 멤버함수 호출할때 인스턴스를 참조해서 호출해야한다.

std::function

  1. 일반 함수 포인터의 문제
    • 반환값이 명시적으로 (암시적 형변환 불가) 같은 타입이 아니면 컴파일 에러
    • 오로지 함수만 호환이 가능하다. (functor, 멤버함수 포인터, 람다함수, bind반환값 등 안됨)
  2. std::function을 사용하는 것의 장점
    • 함수 반환값이 암시적 형변환 가능한 타입이면 대입 가능하다.
    • static 함수 포인터 말고도 functor, 멤버함수 포인터, 람다함수, bind반환값 등 위아더월
    • 유연한 사용이 가능하다. (특히 인자로 함수를 받는 경우. 다양한 활용가능)

std::bind

  1. 일련의 인자들을 함수에 바인딩시킨 함수 객체를 반환하는 함수 템플릿

    • std::bind(함수 포인터, 인자...);
    • 예시

        void show_text(const string& t)
      {
         cout << "TEXT: " << t << endl;
      }
      // show_text 함수에 "Bound function"이라는 문자열을 바인딩시킨
      // function<void ()> 타입을 반환한다.
      function <void ()> f = std::bind(show_text, "Bound function");
  2. std::placeHolder를 활용하여 추가 인수 bind

    • bind의 인자 대신 std::placeHolders::_1, _2, _3... _N을 입력
    • 예시

      int multiply(int a, int b)
      {
      return a * b;
      }
      
      int main()
      {
      // function f 는 multiply 함수에 a 인자는 5로 고정시키고, b 인자는 placeholding만 한다.
      // 이럼으로써, function f는 하나의 인자(x)를 취하지만,
      // 그것이 결국 multiply(5, x)의 형태로 함수를 호출하는 것이다.
      
      auto f = bind(multiply, 5, _1);
      
      for (int i = 0; i < 10; i++)
      {
         cout << "5 * " << i << " = " << f(i) << endl;
      }
      return 0;
      }
  3. 클래스 멤버함수의 경우 this를 바인딩해서 쉽게 사용가능

    • reference_wrapper를 사용하여 인스턴스를 참조로 넘겨야 복사가 발생하지 않는다고 한다.(잘 모르겠음)
    • 예시

      BindTest bt;
      auto f2 = bind(&BindTest::MemberFunc, std::ref(bt), _1);
      f2(10);  // bt.MemberFunc(10);

함수 객체 (functor)

  1. 객체를 함수처럼 사용하는 것
    • 객체의 식별자를 함수 이름처럼 : ()연산자를 오버로딩
    • algorithm함수에서 많이 사용된다.
  2. 함수 포인터와 차이점
    • 멤버 변수, 멤버 함수를 가질 수 있다.
    • template 클래스로 범용적으로 사용할 수 있다.


'컴퓨터 > C++' 카테고리의 다른 글

FSM과 State 패턴  (0) 2015.03.10
c++의 여러가지 생성자들  (0) 2015.03.10
c++의 static 변수에 대하여  (0) 2015.03.10
c++의 가상함수  (0) 2015.03.10