함수 포인터 개념부터 std::function까지
개념 및 기초
- 함수의 시작 번지를 가르키는 포인터 (어셈블리 언어의 호출 스택을 생각해보자)
- 포인터 형식 자체가 다르다.
- 리턴타입(*함수 포인터 이름)(인수목록)
- 함수 : int Func(int a) => 포인터 : int(*funcPtr)(int)
- 함수의 시작번지에 들어가기 앞서 인수들의 목록과 리턴 형식(주소) 정보를 (스택에) 저장해야하므로
함수 포인터 사용 방법
- 대입 & 호출
- 대입 : funcPtr = Func; (괄호없이 단독으로 사용된 함수명은 함수의 시작 번지를 나타내는 포인터 상수.)
- 호출 : 원칙적으로 (*funcPtr)(2); 하지만 컴파일러는 funcPtr(2); 도 동작하게한다.
- 인자가 잘못 들어가거나 리턴형식을 잘못된 타입으로 받으면 당연히 컴파일 에러, 함수 포인터 형식이 복잡한 이유
- 하나의 고유한 타입인 함수 포인터.
- 원형이 다른 함수 포인터끼리 직접 대입, 복사 불가.
int(*funcPtr1)(int), double(*funcPtr2)(float);
funcPtr1 = funcPtr2; //에러
- 캐스팅 가능
int(*funcPtr1)(int), double(*funcPtr2)(float);
funcPtr1 = (int(*)(int))funcPtr2;
- 응용
- 함수 포인터 배열
int(*funcPtrArr[MAX_FUNC_NUM])(int); //함수 포인터 변수명 다음에 배열 크기 선언
funcPtrArr[0] = funcPtr1;
- 함수 포인터의 포인터
int(**funcPPtr)(int); //일단 이건데 불편, 위에도 마찬가지
typedef int(*FUNCPTR)(int); //함수 포인터 타입을 typedef로 정의하고(정의 형식 주의)
FUNCPTR* funcPPtr; //이렇게나
FUNCPTR funcPtr[MAX_FUNC_NUM]; //이렇게 사용하는 것이 가독성이 높다.
함수 포인터의 의미
- 함수를 변수로 사용할 수 있다.
- 하나의 함수포인터만을 가지고 조건에 따라 다른 함수를 실행시킬 수 있다. (fsm에서 활용하는 방법)
- 함수를 인자로 넘길 수 있다.
- 함수 포인터 배열이나 구조체를 사용하여 전체 함수그룹을 변경하는 것도 가능하다.
- 사용하기 좋은 경우
- 조건에 따라 선택해야 할 함수가 두 개 이상인 경우. 함수 포인터 배열을 활용하자
- 함수 선택 분기와 실제 호출 시점이 다른 경우. 미리 선택한뒤 변수에 저장된 함수를 바로 호출하면 된다.
- 호출할 함수가 외부 모듈(DLL)에 있는 경우, 동적 연결하려면 함수 포인터 사용해야한다.
함수 인자 사용하기
- 예제 : 퀵소트
- 퀵소트 형식
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]);
}
클래스 멤버함수의 함수 포인터
- 클래스 내부에 있는 메소드, 멤버함수들을 함수 포인터로 사용하고 싶을때.
- 기존 C의 함수들은 정적(Static)함수로, 소속이나 사용 인스턴스와 무관하게 사용되었다.
- 하지만 클래스의 멤버함수들은 Class 이름을 네임스페이스로 (Class::Function();)
인스턴스에 의존적으로(this->Function();) 사용되므로 차이가 있다. - 예전 스터디 static부분참조
- 실제 코드로 보는 일반/멤버함수 포인터의 차이점과 사용방법 예시
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)(); //원칙적 사용방법 이렇게 쓰도록 한다.
}
- 결론
- 멤버함수를 클래스 외부에서 함수 포인터로 선언(사용)할 때 타입에 클래스 소속을 명시해서 써야한다.
- 멤버함수 호출할때 인스턴스를 참조해서 호출해야한다.
std::function
- 일반 함수 포인터의 문제
- 반환값이 명시적으로 (암시적 형변환 불가) 같은 타입이 아니면 컴파일 에러
- 오로지 함수만 호환이 가능하다. (functor, 멤버함수 포인터, 람다함수, bind반환값 등 안됨)
- std::function을 사용하는 것의 장점
- 함수 반환값이 암시적 형변환 가능한 타입이면 대입 가능하다.
- static 함수 포인터 말고도 functor, 멤버함수 포인터, 람다함수, bind반환값 등 위아더월
- 유연한 사용이 가능하다. (특히 인자로 함수를 받는 경우. 다양한 활용가능)
std::bind
- 일련의 인자들을 함수에 바인딩시킨 함수 객체를 반환하는 함수 템플릿
-
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");
- 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;
} ```
- 클래스 멤버함수의 경우 this를 바인딩해서 쉽게 사용가능
-
reference_wrapper를 사용하여 인스턴스를 참조로 넘겨야 복사가 발생하지 않는다고 한다.(잘 모르겠음)
-
예시
BindTest bt; auto f2 = bind(&BindTest::MemberFunc, std::ref(bt), _1); f2(10); // bt.MemberFunc(10);
함수 객체 (functor)
- 객체를 함수처럼 사용하는 것
- 객체의 식별자를 함수 이름처럼 : ()연산자를 오버로딩
- algorithm함수에서 많이 사용된다.
- 함수 포인터와 차이점
- 멤버 변수, 멤버 함수를 가질 수 있다.
- template 클래스로 범용적으로 사용할 수 있다
퍼옴: https://github.com/jwvg0425/ProjectArthas/wiki/%ED%95%A8%EC%88%98-%ED%8F%AC%EC%9D%B8%ED%84%B0-%EA%B0%9C%EB%85%90%EB%B6%80%ED%84%B0-std::function%EA%B9%8C%EC%A7%80
'C++ > C++ 11 14 17' 카테고리의 다른 글
C++ 11 ENUM (0) | 2018.04.11 |
---|---|
auto keyword (0) | 2018.04.05 |
Aggregate and POD (0) | 2018.04.04 |
R 레퍼런스 타입. (0) | 2017.01.30 |
Modern C++ [MS] (0) | 2016.12.28 |