함수 포인터의 요점은 무엇입니까?


94

함수 포인터의 유용성을 보는 데 문제가 있습니다. 나는 그것이 어떤 경우에 유용 할 것이라고 생각하지만 (결국 존재한다) 함수 포인터를 사용하는 것이 더 낫거나 피할 수없는 경우를 생각할 수 없다.

함수 포인터 (C 또는 C ++)를 잘 사용하는 몇 가지 예를 들어 주시겠습니까?


1
이 관련 SO 질문 에서 함수 포인터에 대한 많은 토론을 찾을 수 있습니다 .
itsmatt 2010

20
@itsmatt : 그렇지 않습니다. "TV는 어떻게 작동합니까?" "TV로 무엇을하나요?"와는 완전히 다른 질문입니다.
sbi

6
C ++에서는 아마도 functor ( en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B )를 사용할 것입니다 .
kennytm 2010

11
C ++가 C로 "컴파일"된 과거의 어두운 시절에는 실제로 가상 메서드가 어떻게 구현되는지 볼 수있었습니다.
sbk

1
관리되는 C ++ 또는 C #과 함께 C ++를 사용하려는 경우 매우 중요합니다. 예 : 델리게이트 및 콜백
Maher

답변:


108

대부분의 예는 졸이다 콜백 : 당신은 함수를 호출 f()하는 다른 함수의 주소를 전달 g()하고, f()호출 g()일부 특정 작업에 대한합니다. 대신 f()주소 를 전달 하면 h()대신 f()콜백 h()합니다.

기본적으로 이것은 함수 를 매개 변수화 하는 방법 입니다. 동작의 일부는으로 하드 코딩되지 않고 f()콜백 함수로 코딩됩니다 . 호출자는 f()다른 콜백 함수를 전달 하여 다르게 행동 할 수 있습니다 . 클래식은 qsort()C 표준 라이브러리에서 가져온 것으로, 정렬 기준을 비교 함수에 대한 포인터로 사용합니다.

C ++에서 이는 종종 함수 객체 (펑 터라 고도 함)를 사용하여 수행됩니다 . 이들은 함수 호출 연산자를 오버로드하는 객체이므로 마치 함수 인 것처럼 호출 할 수 있습니다. 예:

class functor {
  public:
     void operator()(int i) {std::cout << "the answer is: " << i << '\n';}
};

functor f;
f(42);

이 개념은 함수 포인터와 달리 함수 객체가 알고리즘뿐 아니라 데이터도 전달할 수 있다는 것입니다.

class functor {
  public:
     functor(const std::string& prompt) : prompt_(prompt) {}
     void operator()(int i) {std::cout << prompt_ << i << '\n';}
  private:
     std::string prompt_;
};

functor f("the answer is: ");
f(42);

또 다른 장점은 함수 포인터를 통한 호출보다 함수 객체에 대한 호출을 인라인하는 것이 더 쉽다는 것입니다. 이것이 C ++에서 정렬하는 것이 때때로 C에서 정렬하는 것보다 빠른 이유입니다.


1
+1, 다른 예를 보려면이 답변을 참조하십시오. stackoverflow.com/questions/1727824/…
sharptooth

가상 함수를 잊어 버렸습니다. 본질적으로 함수 포인터이기도합니다 (컴파일러가 생성하는 데이터 구조와 결합 됨). 또한 순수 C에서는 이러한 구조를 직접 생성하여 Linux 커널의 VFS 계층 (및 기타 여러 위치)에서 볼 수있는 객체 지향 코드를 작성할 수 있습니다.
Florian

2
@krynr : 가상 함수는 컴파일러 구현 자에게만 해당하는 함수 포인터이며, 어떤 기능이 좋은지 물어봐야한다면 컴파일러의 가상 함수 메커니즘을 구현할 필요가 없을 것입니다.
sbi

@sbi : 물론 맞습니다. 그러나 추상화 내부에서 일어나는 일을 이해하는 것이 도움이된다고 생각합니다. 또한 자신의 vtable을 C로 구현하고 객체 지향 코드를 작성하면 정말 좋은 학습 경험이 있습니다.
Florian

OP에 의해 요구 된 것과 함께 삶과 우주와 모든 것들에 대한 답을 제공하는 브라우니 점
simplename

41

글쎄, 나는 일반적으로 점프 테이블 에서 (전문적으로) 사용합니다 ( 이 StackOverflow 질문 참조 ).

점프 테이블은 유한 상태 머신 에서 일반적으로 (배타적이지는 않음) 사용되어 데이터 기반을 만듭니다. 중첩 된 스위치 / 케이스 대신

  switch (state)
     case A:
       switch (event):
         case e1: ....
         case e2: ....
     case B:
       switch (event):
         case e3: ....
         case e1: ....

함수 포인터의 2d 배열을 만들고 handleEvent[state][event]


24

예 :

  1. 맞춤 정렬 / 검색
  2. 다양한 패턴 (예 : 전략, 관찰자)
  3. 콜백

1
점프 테이블은 그것의 중요한 사용 중 하나입니다.
Ashish

작동하는 예제가 있다면 이것은 내 찬성표를 얻을 것입니다.
Donal Fellows

1
전략과 관찰자는 C ++를 사용할 수있는 경우 가상 기능을 사용하여 더 잘 구현 될 수 있습니다. 그렇지 않으면 +1.
Billy ONeal 2010

함수 포인터를 현명하게 사용하면 관찰자가 더 작고 가벼워 질 수 있다고 생각합니다
Andrey

@BillyONeal GoF 정의를 엄격하게 고수하는 경우에만 Javaism이 누출됩니다. 내가 설명하는 것 std::sort의 ' comp매개 변수 전략으로
Caleth

10

함수 포인터의 유용성에 대한 "고전적인"예 qsort()는 빠른 정렬을 구현하는 C 라이브러리 함수입니다. 사용자가 생각 해낼 수있는 모든 데이터 구조에 대해 보편적이되기 위해서는 정렬 가능한 데이터에 대한 두 개의 void 포인터와 이러한 데이터 구조의 두 요소를 비교하는 방법을 알고있는 함수에 대한 포인터가 필요합니다. 이를 통해 작업에 대해 선택한 기능을 생성 할 수 있으며, 실제로 런타임에 비교 기능을 선택할 수도 있습니다 (예 : 오름차순 또는 내림차순 정렬).


7

위의 모든 사항에 동의하고 .. 런타임에 DLL을 동적으로로드 할 때 함수를 호출하기위한 함수 포인터가 필요합니다.


1
저는 Windows XP를 지원하고 Windows 7 혜택을 계속 사용하기 위해 항상이 작업을 수행합니다. +1.
Billy ONeal 2010

7

나는 여기서 현재에 반대 할 것입니다.

C에서 함수 포인터는 사용자 정의를 구현하는 유일한 방법입니다. OO가 없기 때문입니다.

C ++에서는 동일한 결과에 대해 함수 포인터 또는 펑터 (함수 객체)를 사용할 수 있습니다.

펑 터는 객체 특성으로 인해 원시 함수 포인터에 비해 다음과 같은 여러 장점이 있습니다.

  • 그들은 몇 가지 과부하를 나타낼 수 있습니다. operator()
  • 기존 변수에 대한 상태 / 참조를 가질 수 있습니다.
  • 즉석에서 만들 수 있습니다 ( lambdabind).

저는 개인적으로 함수 포인터보다 함수 포인터를 선호합니다 (상용구 코드에도 불구하고), 주로 함수 포인터 구문이 쉽게 어려울 수 있기 때문입니다 ( Function Pointer Tutorial에서 ).

typedef float(*pt2Func)(float, float);
  // defines a symbol pt2Func, pointer to a (float, float) -> float function

typedef int (TMyClass::*pt2Member)(float, char, char);
  // defines a symbol pt2Member, pointer to a (float, char, char) -> int function
  // belonging to the class TMyClass

펑터가 사용할 수없는 곳에서 함수 포인터가 사용되는 것을 본 것은 Boost.Spirit뿐이었습니다. 그들은 임의의 수의 매개 변수를 단일 템플릿 매개 변수로 전달하기 위해 구문을 완전히 남용했습니다.

 typedef SpecialClass<float(float,float)> class_type;

그러나 가변 템플릿과 람다가 모퉁이에 있기 때문에 우리가 오랫동안 순수한 C ++ 코드에서 함수 포인터를 사용할지 확신 할 수 없습니다.


함수 포인터가 보이지 않는다고해서 사용하지 않는다는 의미는 아닙니다. 컴파일러가 최적화 할 수없는 경우마다 가상 함수를 호출 bind하거나 부스트 function를 사용 하거나 함수 포인터를 사용합니다. 우리는 스마트 포인터를 사용하기 때문에 C ++에서 포인터를 사용하지 않는다고 말하는 것과 같습니다. 어쨌든, 나는 nitpicking입니다.
Florian

3
@krynr : 정중하게 동의하지 않습니다. 중요한 것은 당신이 무엇을 볼 수유형 을 사용하는 것이 구문입니다. 이 모든 것이이면에서 어떻게 작동하는지는 중요하지 않습니다. 이것이 추상화 에 관한 것입니다.
Matthieu M.

5

C에서 고전적인 용도는 qsort 함수입니다 . 여기서 네 번째 매개 변수는 정렬 내에서 정렬을 수행하는 데 사용할 함수에 대한 포인터입니다. C ++에서는 이런 종류의 일에 펑터 (함수처럼 보이는 객체)를 사용하는 경향이 있습니다.


2
@KennyTM : C 표준 라이브러리에서 이것의 유일한 다른 인스턴스를 지적했습니다. 인용 한 예는 타사 라이브러리의 일부입니다.
Billy ONeal 2010

5

최근에 함수 포인터를 사용하여 추상화 계층을 만들었습니다.

임베디드 시스템에서 실행되는 순수 C로 작성된 프로그램이 있습니다. 여러 하드웨어 변형을 지원합니다. 실행중인 하드웨어에 따라 일부 기능의 다른 버전을 호출해야합니다.

초기화시 프로그램은 실행중인 하드웨어를 파악하고 함수 포인터를 채 웁니다. 프로그램의 모든 상위 레벨 루틴은 포인터가 참조하는 함수를 호출합니다. 더 높은 수준의 루틴을 건드리지 않고도 새로운 하드웨어 변형에 대한 지원을 추가 할 수 있습니다.

적절한 기능 버전을 선택하기 위해 switch / case 문을 사용했지만, 프로그램이 점점 더 많은 하드웨어 변형을 지원하도록 성장함에 따라 이것은 비실용적이되었습니다. 나는 사방에 사건 진술을 추가해야했다.

또한 어떤 기능을 사용할지 알아 내기 위해 중간 기능 레이어를 시도했지만 그다지 도움이되지 않았습니다. 새로운 변형을 추가 할 때마다 여러 곳에서 case 문을 업데이트해야했습니다. 함수 포인터를 사용하면 초기화 함수 만 변경하면됩니다.


3

마찬가지로 리치 는 저장 기능을 몇 가지 주소를 참조하기 위해 윈도우 함수 포인터에 매우 일반적이다, 위 말했다.

C languageWindows 플랫폼 에서 프로그래밍 할 때 기본적으로 일부 DLL 파일을 기본 메모리 (사용 LoadLibrary) 에로드하고 DLL에 저장된 함수를 사용하려면 함수 포인터를 만들고 이러한 주소를 가리켜 야합니다 (사용 GetProcAddress).

참조 :


2

C에서 함수 포인터를 사용하여 프로그래밍 할 인터페이스를 만들 수 있습니다. 런타임에 필요한 특정 기능에 따라 다른 구현을 함수 포인터에 할당 할 수 있습니다.


2

저의 주요 용도는 콜백입니다. 나중에 호출 할 함수에 대한 정보를 저장해야 할 때 .

Bomberman을 쓰고 있다고 가정 해 보겠습니다. 사람이 폭탄을 떨어 뜨리고 5 초 후에 폭발해야합니다 ( explode()기능 호출 ).

이제 두 가지 방법이 있습니다. 한 가지 방법은 화면의 모든 폭탄을 "탐색"하여 메인 루프에서 폭발 할 준비가되었는지 확인하는 것입니다.

foreach bomb in game 
   if bomb.boomtime()
       bomb.explode()

또 다른 방법은 시계 시스템에 콜백을 연결하는 것입니다. 폭탄이 설치 되면 시간이 맞을 때 bomb.explode ()를 호출하도록 콜백을 추가합니다 .

// user placed a bomb
Bomb* bomb = new Bomb()
make callback( function=bomb.explode, time=5 seconds ) ;

// IN the main loop:
foreach callback in callbacks
    if callback.timeToRun
         callback.function()

함수 포인터이기 때문에 여기에 모든 함수callback.function() 가있을 수 있습니다 .


질문에 다른 언어 태그가 아닌 [C] 및 [C ++] 태그가 지정되었습니다. 따라서 다른 언어로 코드 스 니펫을 제공하는 것은 주제에서 약간 벗어납니다.
cmaster-모니카 복원

2

함수 포인터 사용

사용자 입력에 따라 동적으로 함수호출합니다 . 이 경우 문자열 및 함수 포인터의 맵을 만듭니다.

#include<iostream>
#include<map>
using namespace std;
//typedef  map<string, int (*)(int x, int y) > funMap;
#define funMap map<string, int (*)(int, int)>
funMap objFunMap;

int Add(int x, int y)
{
    return x+y;
}
int Sub(int x, int y)
{
        return x-y;
}
int Multi(int x, int y)
{
        return x*y;
}
void initializeFunc()
{
        objFunMap["Add"]=Add;
        objFunMap["Sub"]=Sub;
        objFunMap["Multi"]=Multi;
}
int main()
{
    initializeFunc();

    while(1)
    {
        string func;
        cout<<"Enter your choice( 1. Add 2. Sub 3. Multi) : ";
        int no, a, b;
        cin>>no;

        if(no==1)
            func = "Add";
        else if(no==2)
            func = "Sub";
        else if(no==3)
            func = "Multi";
        else 
            break;

        cout<<"\nEnter 2 no :";
                cin>>a>>b;

        //function is called using function pointer based on user input
        //If user input is 2, and a=10, b=3 then below line will expand as "objFuncMap["Sub"](10, 3)"
        int ret = objFunMap[func](a, b);      
        cout<<ret<<endl;
    }
    return 0;
}

이런 식으로 실제 회사 코드에서 함수 포인터를 사용했습니다. 'n'개의 함수를 작성하고이 메서드를 사용하여 호출 할 수 있습니다.

산출:

    선택 항목 입력 (1. 추가 2. 서브 3. 멀티) : 1
    2 no : 2 4 입력
    6
    선택 (1. 추가 2. 서브 3. 멀티) : 2
    2 no : 10 3 입력
    7
    선택 (1. 추가 2. 서브 3. 멀티) : 3
    2 no : 3 6 입력
    18

2

여기에 다른 좋은 답변 외에도 다른 관점이 있습니다.

C에서는 (직접) 함수가 아닌 함수 포인터 사용합니다.

내 말은, 당신은 함수를 작성하지만 함수를 조작 할 수 없다는 것입니다. 사용할 수있는 함수의 런타임 표현은 없습니다. "함수"를 호출 할 수도 없습니다. 당신이 쓸 때 :

my_function(my_arg);

실제로 말하는 것은 " my_function지정된 인수 를 사용하여 포인터에 대한 호출 수행 "입니다. 함수 포인터를 통해 호출하고 있습니다. 이 함수 포인터의 붕괴 는 다음 명령이 이전 함수 호출과 동일 함을 의미합니다.

(&my_function)(my_arg);
(*my_function)(my_arg);
(**my_function)(my_arg);
(&**my_function)(my_arg);
(***my_function)(my_arg);

등등 (@LuuVinhPhuc에게 감사드립니다).

따라서 이미 함수 포인터를 값으로 사용하고 있습니다 . 분명히 당신은 그 값에 대한 변수를 원할 것입니다-그리고 여기에 다른 모든 방법이 들어옵니다 : 다형성 / 사용자 정의 (qsort와 같은), 콜백, 점프 테이블 등.

C ++에서는 람다와 operator(), 심지어 std::function클래스가 있는 객체가 있기 때문에 상황이 조금 더 복잡 하지만 원칙은 여전히 ​​거의 동일합니다.


2
더욱 흥미롭게도, 당신은 함수를 호출 할 수있는 등 (&my_function)(my_arg), (*my_function)(my_arg), (**my_function)(my_arg), (&**my_function)(my_arg), (***my_function)(my_arg)... 때문에 함수는 함수 포인터에 부패
phuclv

1

OO 언어의 경우 장면 뒤에서 다형성 호출을 수행합니다 (내가 생각하는 어느 시점까지 C에도 유효 함).

또한 런타임에 다른 함수 (foo)에 다른 동작을 주입하는 데 매우 유용합니다. 그것은 foo 함수를 고차 함수로 만듭니다. 유연성 외에도 "if-else"의 추가 논리를 끌어낼 수 있기 때문에 foo 코드를 더 읽기 쉽게 만들 수 있습니다.

생성기, 클로저 등과 같은 파이썬에서 다른 많은 유용한 것들을 가능하게합니다.


0

1 바이트 opcode가있는 마이크로 프로세서를 에뮬레이션하기 위해 함수 포인터를 광범위하게 사용합니다. 256 개의 함수 포인터 배열이이를 구현하는 자연스러운 방법입니다.


0

함수 포인터의 한 가지 용도는 함수가 호출되는 코드를 수정하지 않을 수있는 경우 일 수 있습니다 (즉, 호출이 조건부 일 수 있고 다른 조건에서 다른 종류의 처리를 수행해야 함을 의미합니다). 여기서 함수 포인터는 매우 편리합니다. 함수가 호출되는 위치에서 코드를 수정할 필요가 없기 때문입니다. 적절한 인수와 함께 함수 포인터를 사용하여 함수를 호출하기 만하면됩니다. 함수 포인터는 조건부로 다른 함수를 가리 키도록 만들 수 있습니다. (초기화 단계에서 수행 할 수 있습니다.) 또한 위의 모델은 호출되는 코드를 수정할 수없는 경우 매우 유용합니다 (수정할 수없는 라이브러리 API라고 가정). API는 적절한 사용자 정의 함수를 호출하기 위해 함수 포인터를 사용합니다.


0

여기에 다소 포괄적 인 목록을 제공하려고합니다.

  • 콜백 : 사용자가 제공 한 코드로 일부 (라이브러리) 기능을 사용자 지정합니다. 프라임 예제는 qsort()이지만 이벤트를 처리하는 데 유용하거나 (예 : 클릭 할 때 콜백을 호출하는 버튼) 스레드를 시작하는 데 필요합니다 ( pthread_create()).

  • 다형성 : C ++ 클래스의 vtable은 함수 포인터의 테이블 일뿐입니다. 그리고 C 프로그램은 일부 객체에 대해 vtable을 제공하도록 선택할 수도 있습니다.

    struct Base;
    struct Base_vtable {
        void (*destruct)(struct Base* me);
    };
    struct Base {
        struct Base_vtable* vtable;
    };
    
    struct Derived;
    struct Derived_vtable {
        struct Base_vtable;
        void (*frobnicate)(struct Derived* me);
    };
    struct Derived {
        struct Base;
        int bar, baz;
    }

    Derived그런 다음 의 생성자는 vtable파생 클래스의 destructand 구현 을 사용하여 멤버 변수를 전역 개체로 설정하고 a frobnicate를 소멸시키는 데 필요한 코드 struct Base*는 단순히를 호출합니다 base->vtable->destruct(base). 이는 파생 클래스가 base실제로 가리키는 것과는 별개로 소멸자의 올바른 버전을 호출합니다. .

    함수 포인터가 없으면 다형성은 다음과 같은 스위치 구조로 코딩되어야합니다.

    switch(me->type) {
        case TYPE_BASE: base_implementation(); break;
        case TYPE_DERIVED1: derived1_implementation(); break;
        case TYPE_DERIVED2: derived2_implementation(); break;
        case TYPE_DERIVED3: derived3_implementation(); break;
    }

    이것은 다소 빠르게 다루기 어려워집니다.

  • 동적으로로드 된 코드 : 프로그램이 모듈을 메모리로로드하고 해당 코드를 호출하려고 할 때 함수 포인터를 통과해야합니다.

내가 본 함수 포인터의 모든 사용은이 세 가지 광범위한 클래스 중 하나에 속합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.