함수 포인터의 유용성을 보는 데 문제가 있습니다. 나는 그것이 어떤 경우에 유용 할 것이라고 생각하지만 (결국 존재한다) 함수 포인터를 사용하는 것이 더 낫거나 피할 수없는 경우를 생각할 수 없다.
함수 포인터 (C 또는 C ++)를 잘 사용하는 몇 가지 예를 들어 주시겠습니까?
함수 포인터의 유용성을 보는 데 문제가 있습니다. 나는 그것이 어떤 경우에 유용 할 것이라고 생각하지만 (결국 존재한다) 함수 포인터를 사용하는 것이 더 낫거나 피할 수없는 경우를 생각할 수 없다.
함수 포인터 (C 또는 C ++)를 잘 사용하는 몇 가지 예를 들어 주시겠습니까?
답변:
대부분의 예는 졸이다 콜백 : 당신은 함수를 호출 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에서 정렬하는 것보다 빠른 이유입니다.
글쎄, 나는 일반적으로 점프 테이블 에서 (전문적으로) 사용합니다 ( 이 StackOverflow 질문 참조 ).
점프 테이블은 유한 상태 머신 에서 일반적으로 (배타적이지는 않음) 사용되어 데이터 기반을 만듭니다. 중첩 된 스위치 / 케이스 대신
switch (state)
case A:
switch (event):
case e1: ....
case e2: ....
case B:
switch (event):
case e3: ....
case e1: ....
함수 포인터의 2d 배열을 만들고 handleEvent[state][event]
예 :
std::sort
의 ' comp
매개 변수 전략으로
위의 모든 사항에 동의하고 .. 런타임에 DLL을 동적으로로드 할 때 함수를 호출하기위한 함수 포인터가 필요합니다.
나는 여기서 현재에 반대 할 것입니다.
C에서 함수 포인터는 사용자 정의를 구현하는 유일한 방법입니다. OO가 없기 때문입니다.
C ++에서는 동일한 결과에 대해 함수 포인터 또는 펑터 (함수 객체)를 사용할 수 있습니다.
펑 터는 객체 특성으로 인해 원시 함수 포인터에 비해 다음과 같은 여러 장점이 있습니다.
operator()
lambda
및 bind
).저는 개인적으로 함수 포인터보다 함수 포인터를 선호합니다 (상용구 코드에도 불구하고), 주로 함수 포인터 구문이 쉽게 어려울 수 있기 때문입니다 ( 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입니다.
C에서 고전적인 용도는 qsort 함수입니다 . 여기서 네 번째 매개 변수는 정렬 내에서 정렬을 수행하는 데 사용할 함수에 대한 포인터입니다. C ++에서는 이런 종류의 일에 펑터 (함수처럼 보이는 객체)를 사용하는 경향이 있습니다.
최근에 함수 포인터를 사용하여 추상화 계층을 만들었습니다.
임베디드 시스템에서 실행되는 순수 C로 작성된 프로그램이 있습니다. 여러 하드웨어 변형을 지원합니다. 실행중인 하드웨어에 따라 일부 기능의 다른 버전을 호출해야합니다.
초기화시 프로그램은 실행중인 하드웨어를 파악하고 함수 포인터를 채 웁니다. 프로그램의 모든 상위 레벨 루틴은 포인터가 참조하는 함수를 호출합니다. 더 높은 수준의 루틴을 건드리지 않고도 새로운 하드웨어 변형에 대한 지원을 추가 할 수 있습니다.
적절한 기능 버전을 선택하기 위해 switch / case 문을 사용했지만, 프로그램이 점점 더 많은 하드웨어 변형을 지원하도록 성장함에 따라 이것은 비실용적이되었습니다. 나는 사방에 사건 진술을 추가해야했다.
또한 어떤 기능을 사용할지 알아 내기 위해 중간 기능 레이어를 시도했지만 그다지 도움이되지 않았습니다. 새로운 변형을 추가 할 때마다 여러 곳에서 case 문을 업데이트해야했습니다. 함수 포인터를 사용하면 초기화 함수 만 변경하면됩니다.
마찬가지로 리치 는 저장 기능을 몇 가지 주소를 참조하기 위해 윈도우 함수 포인터에 매우 일반적이다, 위 말했다.
C language
Windows 플랫폼 에서 프로그래밍 할 때 기본적으로 일부 DLL 파일을 기본 메모리 (사용 LoadLibrary
) 에로드하고 DLL에 저장된 함수를 사용하려면 함수 포인터를 만들고 이러한 주소를 가리켜 야합니다 (사용 GetProcAddress
).
참조 :
저의 주요 용도는 콜백입니다. 나중에 호출 할 함수에 대한 정보를 저장해야 할 때 .
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()
가있을 수 있습니다 .
함수 포인터 사용
사용자 입력에 따라 동적으로 함수 를 호출합니다 . 이 경우 문자열 및 함수 포인터의 맵을 만듭니다.
#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
여기에 다른 좋은 답변 외에도 다른 관점이 있습니다.
내 말은, 당신은 함수를 작성하지만 함수를 조작 할 수 없다는 것입니다. 사용할 수있는 함수의 런타임 표현은 없습니다. "함수"를 호출 할 수도 없습니다. 당신이 쓸 때 :
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
클래스가 있는 객체가 있기 때문에 상황이 조금 더 복잡 하지만 원칙은 여전히 거의 동일합니다.
(&my_function)(my_arg)
, (*my_function)(my_arg)
, (**my_function)(my_arg)
, (&**my_function)(my_arg)
, (***my_function)(my_arg)
... 때문에 함수는 함수 포인터에 부패
함수 포인터의 한 가지 용도는 함수가 호출되는 코드를 수정하지 않을 수있는 경우 일 수 있습니다 (즉, 호출이 조건부 일 수 있고 다른 조건에서 다른 종류의 처리를 수행해야 함을 의미합니다). 여기서 함수 포인터는 매우 편리합니다. 함수가 호출되는 위치에서 코드를 수정할 필요가 없기 때문입니다. 적절한 인수와 함께 함수 포인터를 사용하여 함수를 호출하기 만하면됩니다. 함수 포인터는 조건부로 다른 함수를 가리 키도록 만들 수 있습니다. (초기화 단계에서 수행 할 수 있습니다.) 또한 위의 모델은 호출되는 코드를 수정할 수없는 경우 매우 유용합니다 (수정할 수없는 라이브러리 API라고 가정). API는 적절한 사용자 정의 함수를 호출하기 위해 함수 포인터를 사용합니다.
여기에 다소 포괄적 인 목록을 제공하려고합니다.
콜백 : 사용자가 제공 한 코드로 일부 (라이브러리) 기능을 사용자 지정합니다. 프라임 예제는 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
파생 클래스의 destruct
and 구현 을 사용하여 멤버 변수를 전역 개체로 설정하고 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;
}
이것은 다소 빠르게 다루기 어려워집니다.
동적으로로드 된 코드 : 프로그램이 모듈을 메모리로로드하고 해당 코드를 호출하려고 할 때 함수 포인터를 통과해야합니다.
내가 본 함수 포인터의 모든 사용은이 세 가지 광범위한 클래스 중 하나에 속합니다.