함수 포인터

함수 포인터 구문을 알고 있을 것이다. 함수를 선언하고, 해당하는 함수를 가리키는 함수 포인터를 선언할 수 있다. 그렇다면 함수는 무엇인지가 중요해지는데, C/C++에서 함수의 이름은 함수가 시작하는 메모리 주소를 가리키는 상수로 해석할 수 있다. 마치 문자열이 문자열의 시작 주소를 가리키는 상수인 것처럼 말이다. 아래의 코드를 보자

#include <iostream>

using namespace std;

double multiplyByTwo(int num){
    return num * 2.0;
}

int main(){

    double (*fp)(int);
    double (*fp2)(int);

    fp = multiplyByTwo;
    fp2 = &multiplyByTwo;
    //함수는 참조연산자(혹은 주소 연산자, 즉 &)를 사용하면 자기 자신을 rvalue 함수포인터 형태로 돌려준다.
    //함수 이름을 그대로 써도 함수는 자신의 메모리주소를 나타낸다. 즉 그것도 일종의 포인터다.
    //저 문장 두 개를 다 지원하는 것이 모든 문제의 시작이라고 할 수 있다.
    //정리 : 함수는 주소도 있고..값도 주소 자체와 같다.

    cout << (int*)multiplyByTwo << " " << (int*)&multiplyByTwo << endl;
    //동일한 주소값을 가진다. 같은 값이지만 타입이 다르다.
    //int*로 형변환 한 이유는 그렇게 하지 않으면 cout에 함수포인터를 출력하는 연산이 없기에 자동으로 boolean으로 변환되어 1로 출력되기 때문이다.

    cout << (*fp)(2) << " " << (*fp2)(2) << endl;
    //함수 포인터를 역참조해서 함수로 만들고 다시 호출하는, 복잡하지만 상식적인 구문

    cout << fp(2) << " " << fp2(2) << endl;
    //함수 포인터를 역참조하지 않고도 정상적으로 호출하는 모습.
    //가능한 이유는 함수의 이름 자체가 이미 주소값이므로, 그 주소값을 그대로
    //복사해 온 함수 포인터도 사실 함수와 동일한 것이어야 한다는 의미다.
    //즉 함수라는 말이 이미 함수 포인터와 동의어라는 논리이다.

    cout << (*(&fp))(2) << " " << (*********fp)(6) << " " << (**************multiplyByTwo)(4) << endl;
    //함수 포인터는 참조연산자(혹은 주소 연산자, 즉 &)를 사용하면 함수 포인터에 대한 포인터가 된다.
    //그리고 그것을 역참조하면 함수포인터가 된다.
    //그리고 함수 포인터는 ()로 바로 호출할 수 있다.
    //또한 함수 포인터에 역참조 연산자를 사용하면 당연히 함수를 돌려준다.
    //함수 이름에 대한 역참조는 자신을 돌려줄 뿐이다.
    //마찬가지로 함수도 호출할 수 있다.(당연히 함수는 원래 호출할 수 있다.)

    return 0;

}

혼란스러운 모습이지만, C언어가 저런 결정을 한 것에는 함수이름과 함수 포인터가 모두 동일하게 함수의 포인터로써 동작하게 하려는 의도가 있다. 위의 주석에 정리한 내용이지만 가장 핵심은 이것이다. 함수는 주소도 있고..값도 주소 자체와 같다.

타입아이디 살펴보기

이런 코드를 추가해서 타입아이디를 살펴보면 값은 이렇다.

cout << typeid(multiplyByTwo).name() << endl;
cout << typeid(&multiplyByTwo).name() << endl;
cout << typeid(*multiplyByTwo).name() << endl;
cout << typeid(**multiplyByTwo).name() << endl;
cout << typeid(fp).name() << endl;
cout << typeid(&fp).name() << endl;
cout << typeid(****fp).name() << endl;
FdiE
PFdiE
FdiE
FdiE
PFdiE
PPFdiE
FdiE

결국 앞서 말한 C언어의 동작의도를 기억하고, 함수 포인터 구문이 이렇다고 이해하면 되겠다.

참고

https://stackoverflow.com/questions/21316671/how-do-both-of-these-function-pointer-calling-syntax-variations-work