본문 바로가기
NC University/Advanced C++

4일차 - 범용적 함수 포인터 (general-purpose function pointer)

by 날쑤 2015. 7. 21.

Intro

  다음은 단순한 Button 클래스를 구현하고 이를 테스트하는 코드이다.

  Button 객체는 인자도 없고, 반환값도 없는 함수 하나를 받아서 핸들러로 지정한다. 그리고 클릭 이벤트가 발생하면 지정된 핸들러를 호출하도록 되어있다. 큰 문제는 없어보이지만 만약에 핸들러를 일반 함수가 아닌 객체의 멤버의 함수를 지정하고 싶은 경우는 어떻게 해야 할까? handler를 멤버함수 포인터 타입으로 선언하면 될 것 같지만, 이렇게하면 반대로 일반 함수를 핸들러로 지정할 수가 없다.

std::function

  C++ 표준에서는 범용적 함수 포인터 역할을 위해 std::function 클래스를 제공한다. std::function 객체는 C++에서 호출가능한(callable) 모든 대상(함수, 람다 표현식, 함수 객체 등)을 저장하고 그 대상을 호출할 수 있다. 그리고 위의 경우에서 문제가 됐었던 일반 함수/멤버 함수 포인터도 동시에 커버할 수 있다. 다음은 std::function을 사용하는 간단한 예제를 보여준다.

  기본적인 사용법은 우선 타입인자를 함수 원형(signature)으로 줘서 function 객체를 생성하고, 거기에 대상(target) 함수의 주소를 대입한다. 첫번째 호출은 function 객체를 만들 때 지정된 함수 타입과 대상 함수의 타입이 일치하기 때문에 크게 이상한 점이 없다. 그런데 'goo'와 'hoo'는 각각 1개와 2개의 인자를 필요로 한다. 인자의 개수가 다르니깐 문제가 있을 것 같지만 위의 코드는 문제없이 수행이 된다. 이것은 std::bind를 이용해서 함수 주소뿐만 아니라 호출 시에 필요한 인자들도 함께 묶어서 function 객체로 전달하기 때문이다. 마찬가지로 멤버함수 호출시에는 객체의 주소를 인자라고 생각하면 앞의 경우와 유사한 케이스가 되므로 이또한 크게 문제가 되지 않는다.

std::bind

  이처럼 std::bind는 M개의 인자를 갖는 함수(정확히는 모든 callable object)를 N개의 인자를 가지는 함수로 바꾸는 도구로써, C++ 11의 핵심 라이브러리 기술 중 하나이다. 이번에는 bind를 활용하는 약간 다른 케이스를 살펴보자.

  이번에는 f가 하나의 정수를 인자를 받는 함수의 주소를 관리하도록 정의되었다. 그래서 'goo'는 bind 없이도 함수 주소를 f에 대입할 수 있으며, 호출도 가능하다. 그러나 'hoo'는 상황이 약간 다르다. 'hoo'는 두 개의 인자가 필요하기 때문에 그냥은 function 객체에 대입할 수 없다. 그러므로 여기서도 bind를 사용해야 한다.

  하지만 앞의 예제에서는 두 개의 인자를 모두 픽스시키면 됐지만, 이번에는 두 개의 인자 중 하나만 픽스를 시켜야 한다. 그래서 마지막 줄에서 bind로 'hoo'와 하나의 인자를 픽스시켜서 묶었지만, 이것 역시 컴파일 에러가 발생한다. 이는 bind의 가변인수의 개수는 묶을 함수의 인수 개수와 일치해야 하기 때문이다.(즉, 'hoo'는 2개의 인자를 필요로 하므로 bind의 가변인자 개수도 두개여야한다.)

std::placeholder

  placeholder는 위와 같은 상황에서 이용가능한 문법으로써, function 객체 호출 시에 넘어오는 인자를 target 함수로 mapping시켜준다. placeholder를 이용하면 위의 문제는 아래와 같이 해결할 수 있다.

  bind를 할 때 사용되는 _N을 placeholder라고 한다. 만약 placeholder _N이 bind의 가변인자 중 M번째 위치에 있다면, 이것의 의미는 'function 객체로 넘어오는 N번째 인자를 target 함수의 M번째 인자로 사용한다.'라는 것이다. 위의 경우에는 hoo의 첫번째 인자는 3이고, 두 번째 인자는 f를 호출할 때 넘어오는 첫 번째 인자를 쓰겠다는 것이다. 그러므로 f(5)는 hoo(3, 5)와 동일한 의미가 된다.

bind와 placeholder를 이용하는 간단한 문제

정리

  기존의 함수 포인터로는 같은 함수 원형이라고 하더라도 대상 함수가 클래스의 멤버함수냐 일반 함수냐에 따라 사용 범위가 제한되었다. function 객체와 bind 함수는 이러한 제한을 없애주었다. 여기에 더해 placeholder는 target 함수와 function 객체를 정의할 때 명시한 함수원형의 인자 개수가 다른 경우에도 하나의 function 객체에 저장할 수 있도록 도와준다. 이처럼 C++ 11에서는 기존 C++의 함수 포인터의 한계를 뛰어넘은 범용적 함수 포인터를 제공한다.