본문 바로가기

함수 포인터 (Function pointer) 본문

💘 C++/함수

함수 포인터 (Function pointer)

Hyonii 2022. 8. 8. 11:37

함수 포인터 (Function pointer)

포인터가 다른 변수의 주소를 저장하는 변수라는 것을 배웠다

이와 유사하게 함수 포인터는 함수를 가리키는 변수이다.

즉, 함수의 주소를 저장하는 변수이다.

 

5를 리턴하는 아주 간단한 함수이다.

식별자 func는 함수의 이름이다.

함수의 타입은 무엇일까? 

함수는 고유한 l-value 함수 타입이다.

위 예제의 경우 정수를 반환하고 매개 변수를 받지 않는 함수 타입이다

 

cout으로 함수를 출력해보자.

여기서는 함수( ) <- 괄호가 아니라 그냥 함수 이름만! 넣었다.

 

주소값이 나온다.

함수도 포인터이다.

변수와 마찬가지로 함수는 메모리의 할당된 주소에 있다.

함수도 메모리에 들어온다.

 

() 연산자를 통해 함수를 호출하면, 호출되는 함수의 주소로 점프하여 실행시키는 것이다.

 

main에서 이 func 함수를 호출을 하게 되면

이 함수가 어느 메모리 주소에 있는지를 알아내고

그 주소에 있는 프로그램을 가져다가 실행시키는 것이다.

 

이때 나중에 돌아올 때는 어디로 와야 되는지도 가지고 있게 된다.

이 부분에 대해서는 곧 스택과 힙에대해서 설명할 때 자세하게 설명하겠다.

 

일단 여기서 알아야 할 것은 함수도 주소를 갖고 있다는 것이다.

 


함수에 대한 포인터 (Pointer to function)

비 상수 함수 포인터(non-const function pointer) 생성하는 문법은 C++에서 볼 수 있는 못생긴 문법 중 하나다.

 

지금 func()라는 함수는 파라미터가 없다. int(*fcnptr)(); 얘도 파라미터가 없다. 그리고 둘다 리턴값이 int다. 차이가 있다면 *이 있다.

위 코드에서  fcnptr은 인수가 없고 정수를 반환하는 함수에 대한 포인터이다. (즉, 함수 포인터)

그러므로 이 타입과 같은(func 같은) 함수를 가리킬 수 있다. 

괄호가 필요한 이유는 int*fcnptr() 과 같은 코드는

정수에 대한 포인터를 반환하는 인수가 없는 함수 fcptr의 전방 선언으로 해석되기 때문에 우선순위를 위해서다.

 

또한, 오류를 발생시키는 흔한 실수는 아래와 같다.

fcnptr = goo();

상수 함수 포인터를 만들기 위해서는 * 뒤에 const 키워드를 사용하면 된다.

int(*const fcnptr)();

만약 int 앞에 const가 오면 함수가 const int를 반환한다는 것을 의미한다.


함수 포인터에 함수 할당

함수 포인터는 함수로 초기화 할 수 있다.

 

초기화는 이렇게 해주면 된다.

이때 주의해야 할 점이 있다.

 

이렇게 뒤에 ( ) 괄호까지 치면

func() 함수를 실행을 시켜서 그 결과값을 받아온다는 의미다.

지금 하고 싶은 것은 함수의 주소를 가져오는 거니까

 

이렇게 초기화를 해줘야 한다.

이렇게 리턴타입이 역시 int고, 매개변수가 없고,

10을 리턴해주는 goo()라는 함수가 있다면

함수 포인터를 중간에 goo로 바꿔치기할 수도 있다.

 

실행을 해보자.

이 함수 포인터가 가르키고 있는 것은 func다.

5가 출력이 될것이다.

그다음에 함수 포인터 변수에 다른 함수의 포인터(주소)를 넣어주었다.

그리고 똑같은 함수 포인터를 실행을 시켜보자.

 

윗줄에서는 func 함수가 실행되어서 5가 나오고

아랫줄에서는 goo 함수가 실행되어서 10이 나왔다.

 

나중에 객체지향에서 여러 가지 프로그래밍 방법들을 배울 거고

특히 다형성 같은 것들을 배우게 될 것이다.

그때 지금 배운 함수 포인터의 개념이 큰 도움이 될 것이다.

 

당연히 함수포인터 변수의 타입은 (파라미터가 어떻게 되어있는지 , 리턴 타입이 어떤지는)

대입하려고 하는 함수의 파라미터, 리턴 타입과 정확히 맞아야 한다.

 

func 함수에 매개변수 int x를 넣어줬더니 에러가 뜬다.

왜냐하면 int(*fcnptr)() 는 지금 매개변수가 없는 포인터로 선언되어있기 때문이다.

이럴 때는

 

int(*fcnptr)() 괄호 안에 int를 넣어주면 된다.

밑에는 아무것도 안 들어갔으니까 문제가 생겼다.

 

값을 넣어주면 되고

밑에 또 문제가 생긴 것은

 

goo 함수가 매개변수가 달라서다.

매개변수와 리턴 타입을 맞춰줘야 함수 포인터를 대입해서 사용할 수 있다.

 


다른 함수의 파라미터로 함수 전달하기

함수 포인터의 가장 유용한 기능은 함수를 다른 함수에 전달하는 것이다.

다른 함수에 대한 인수로써 사용되는 함수를 콜백 함수라고 부르기도 한다.

 

간단한 예제를 만들어보자.

 

10개의 숫자가 들어간 array가 하나 있다.

for-each를 이용해서 짝수일 때만 출력을 하는 코드를 짜 보자.

 

짝수만 나오고 있다.

얘를 함수로 한번 만들어 보자.

 

이렇게 넣어주면 똑같이 작동한다.

경우에 따라서 홀수 출력을 하고 싶을 수도 있다.

 

이렇게 하고 true를 넣어주면 짝수가 출력되고 false를 넣어주면 홀수가 출력된다.

설명을 위해서 여기에 함수 포인터를 사용해보자.

이 개념이 나중에 객체지향과 연결된다.

 

짝수면 true를 리턴하는 isEven 함수를 만들고

홀수면 true를 리턴하는 isOdd 함수를 만들었다.

 

그다음에 여기다가 bool 대신에 기능을 넣어주어야 한다.

함수를 넣어준다.

 

리턴 타입이 bool이다.

check_fcn이라고 이름을 지어주었고 정수가 하나 들어간다.

그러니까 이 함수 포인터 자리에 isEven이나 isOdd 둘 중 하나가 들어갈 수 있는 것이다.

 

check_fcn(element)가 true면 출력을 한다라고 하면 밑에 한줄은 지워버릴 수 있다.
이 true, false 자리에 함수를 넣어보자

함수를 넣었는데 오류가 생긴 것은

const int인 isEven과 타입이 맞지 않기 때문이다.

 

이렇게 고치면 포인터가 잘 들어간다.

참고로 함수는 그 이름 자체가 포인터이기 때문에 앞에 주소 연산자 &를 사용할 필요가 없다.

헷갈릴 수 있으니 잘 기억하자

 

실행시켜보면

 

모든 경로에 return 값이 있는게 아니다라고 경고가 뜬 것은
이렇게 붙여주면 된다.
이렇게 함수를 넣어서
다른 함수에 있는 기능을 바꿔버렸다. 이 기능이 나중에 다형성을 이해하실때 크게 도움 될 것이다.

그리고 바로 이전에 default parameter 기본 매개 변수에 대해서도 공부했었다.

여기에서도 기본값을 넣어 줄 수 있다.

 

이렇게 할 수 있다.

main에서 파라미터를 안 넣어주면 기본값이 들어가니까

실행시켜보면

 

결과는 똑같이 나온다.

 


함수 포인터를 더 예쁘게 만들기

함수 포인터를 너무 빈번하게 사용하면

 

이렇게 매번 타이핑하기 귀찮을 수 있다.

그럴 때는 typedef으로 줄일 수 있다.

 

이렇게 해주면 마치 check_fcn_t가 type인 것처럼 처리가 된다.

 

문제 없음

함수 포인터도 typedef으로 줄여서 사용할 수 있다.

그리고 typealias도 쓸 수 있다.

C++에서는 using을 사용해서 함수 포인터 타입에 대한 별칭을 만들 수 있다.

 

이렇게 써주면 된다.

문법상 이렇게 깔끔하게 쓸 수 있다는 것을 알아두면 도움이 된다.

깔끔하게 정리하는 것도 좋은데 더 편한 것이 있다.

 


C++에서의 std::function

C++에서는 표준 라이브러리 <functional> 헤더에 정의되어 있는 std::function을 이용해서 함수 포인터를 정의할 수 있다.

요즘 함수 포인터보다 잘 쓰인다.

어떻게 사용하냐면 std::function<반환타입(인수...)> 이렇게 써주면 된다.

 

return 타입은 bool이고 const int&를 넣어주고 꺽쇠 닫아버리면 됨.

보시다시피, 반환 타입과 매개 변수는 꺽쇠안에 들어 있다.

매개 변수를 꺽쇠 속 괄호 안에 둠으로써 좀 더 명시적으로 읽을 수 있다.

매개 변수가 없다면 괄호 안의 내용을 비워두면 된다.

 

이렇게 isEven 함수를 넣어줄 수 있다.

받는 쪽에서도 이거를

 

이렇게 넣어 줄 수 있다.

default는 빼줌.

 

main에서는

printNumbers();에 fcnptr을 넣어주고 실행시킨 다음

fcnptr에 isOdd를 넣고 다시 함수를 실행시키면

 

했던 대로 정상적으로 잘 작동한다.

이 std::function은 아주 기본적인 용법만 알려드린 것이다.

얘는 나중에 아주 다양하고 유용하게 쓰일 수 있다.

이것은 추후 자세히 설명 예정.

 

일단 여기서는 function도 pointer가 있다는 것을 이해하고

기능을 수행하는 것을 파라미터로 넣어줄 수 있다는 새로운 개념을 받아들이면 좋다.

 

function pointer를 이용하면 프로그램이 훨씬 다재다능해진다.

신기한 형태로 구성이 될 수도 있고

결과적으로 프로그래머가 복잡하고 많은 기능들을

비교적 간단하게 구현할 수 있게 하는 기술을 갖출 수 있게 해 준다.

Comments