11.2 Function Pointers

주홍영·2022년 3월 14일
0

Learncpp.com

목록 보기
116/199

https://www.learncpp.com/cpp-tutorial/function-pointers/

function의 identifier는 어떠한 의미를 가지고 있을까?

int foo() // code for foo starts at memory address 0x002717f0
{
    return 5;
}

int main()
{
    foo(); // jump to address 0x002717f0

    return 0;
}

위 코드를 보면 foo()라는 것은 foo의 address로 jump하라는 의미를 가진다

#include <iostream>

int foo() // code starts at memory address 0x002717f0
{
    return 5;
}

int main()
{
    std::cout << foo << '\n'; // we meant to call foo(), but instead we're printing foo itself!

    return 0;
}

그리고 위 코드에서 처럼 foo라는 이름 자체는 function이 정의된 address를 말하고 있다
따라서 foo를 그대로 출력하면 주소가 나온다

출력예시

0x002717f0

위와 같은 결과를 의도하지 않은 function call을 의도했다면 다음과 같이 다시 쓸 수 있다

#include <iostream>

int foo() // code starts at memory address 0x002717f0
{
    return 5;
}

int main()
{
    std::cout << reinterpret_cast<void*>(foo) << '\n'; // Tell C++ to interpret function foo as a void pointer

    return 0;
}

위 코드에서 처럼 reinterpre_cast< void* > 를 이용해 foo()를 호출한다

Pointers to functions

function pointer는 다음과 같은 모습으로 선언할 수 있다

// fcnPtr is a pointer to a function that takes no arguments and returns an integer
int (*fcnPtr)();

위해서 fcnptr은 int return type을 가진 argument가 없는 함수의 func pointer를
선언한 것이다

그리고 const pointer와 마찬가지로 fcnptr을 const로 활용하기 위해서는
asterisk뒤에 const가 위치하도록 만들어 준다

int (*const fcnPtr)();

Assigning a function to a function pointer

int foo()
{
    return 5;
}

int goo()
{
    return 6;
}

int main()
{
    int (*fcnPtr)(){ &foo }; // fcnPtr points to function foo
    fcnPtr = &goo; // fcnPtr now points to function goo

    return 0;
}

function pointer에는 function에 접근할 수 있는 주소가 담겨있는 주소를 저장한다
따라서 foo의 주소를 할당한다
foo : 함수에 접근하는 주소
&foo : foo가 담겨있는 주소

흔히하는 실수가 fcnptr = goo(); 와 같이 하는 것이다
그리고 pointer이기 때문에 nullptr로 초기화해도 괜찮다

int (*fcnptr)() { nullptr }; // okay

Calling a function using a function pointer

그렇다면 fcnptr에는 foo의 주소가 담겨져 있으므로 fcnptr에 역추적은 함수에 접근하는 주소를 반환한다 따라서 function pointer로 함수를 호출하기 위해서는 다음과 같이 사용한다

int foo(int x)
{
    return x;
}

int main()
{
    int (*fcnPtr)(int){ &foo }; // Initialize fcnPtr with function foo
    (*fcnPtr)(5); // call function foo(5) through fcnPtr.

    return 0;
}

생소하지만 일반 pointer와 function pointer의 큰 차이점 중 하나는
instantiate할 때와 사용할 때 ()를 꼭 사용한다는 것이다

두번째 방법은 implicit dereference를 사용하는 것이다

int foo(int x)
{
    return x;
}

int main()
{
    int (*fcnPtr)(int){ &foo }; // Initialize fcnPtr with function foo
    fcnPtr(5); // call function foo(5) through fcnPtr.

    return 0;
}

엄밀하게 따지면 위의 문법은 말이 안되어야 하지만 implicit dereference로 인해
함수를 호출하는 것이 문제없이 이뤄지고 있다
하지만 몇몇 오래된 컴파일러는 지원하지 않는 표현이다
but 현대의 컴파일러는 반드시 지원할 것이다

참고로 function pointer로는 default parameter를 지원하지 않을 수 있다
왜냐하면 function pointer를 통한 호출은 run-time에 이뤄지고
default parameter는 compile-time에 이뤄지기 때문이다
따라서 우리가 직접 넣어줘야 한다

그리고 앞서 function pointer는 nullptr로 초기화될 수 있다고 이야기 했다
따라서 null check을 통해서 프로그램이 crash나는 일이 없도록 장치를 취하는 것을 추천한다

int foo(int x)
{
    return x;
}

int main()
{
    int (*fcnPtr)(int){ &foo }; // Initialize fcnPtr with function foo
    if (fcnPtr) // make sure fcnPtr isn't a null pointer
        fcnPtr(5); // otherwise this will lead to undefined behavior

    return 0;
}

위에서 if(fcnPtr)로 null check를 하고 있다

Passing functions as arguments to other functions

function pointer의 가장 큰 활용처중 하나는 다른 함수에 argument로 pass하는 것이다
이렇게 사용되는 함수를 callback function이라고 한다

사용예시는 다음과 같다

#include <utility> // for std::swap
#include <iostream>

// Note our user-defined comparison is the third parameter
void selectionSort(int* array, int size, bool (*comparisonFcn)(int, int))
{
    // Step through each element of the array
    for (int startIndex{ 0 }; startIndex < (size - 1); ++startIndex)
    {
        // bestIndex is the index of the smallest/largest element we've encountered so far.
        int bestIndex{ startIndex };

        // Look for smallest/largest element remaining in the array (starting at startIndex+1)
        for (int currentIndex{ startIndex + 1 }; currentIndex < size; ++currentIndex)
        {
            // If the current element is smaller/larger than our previously found smallest
            if (comparisonFcn(array[bestIndex], array[currentIndex])) // COMPARISON DONE HERE
            {
                // This is the new smallest/largest number for this iteration
                bestIndex = currentIndex;
            }
        }

        // Swap our start element with our smallest/largest element
        std::swap(array[startIndex], array[bestIndex]);
    }
}

// Here is a comparison function that sorts in ascending order
// (Note: it's exactly the same as the previous ascending() function)
bool ascending(int x, int y)
{
    return x > y; // swap if the first element is greater than the second
}

// Here is a comparison function that sorts in descending order
bool descending(int x, int y)
{
    return x < y; // swap if the second element is greater than the first
}

// This function prints out the values in the array
void printArray(int* array, int size)
{
    for (int index{ 0 }; index < size; ++index)
    {
        std::cout << array[index] << ' ';
    }

    std::cout << '\n';
}

int main()
{
    int array[9]{ 3, 7, 9, 5, 6, 1, 8, 2, 4 };

    // Sort the array in descending order using the descending() function
    selectionSort(array, 9, descending);
    printArray(array, 9);

    // Sort the array in ascending order using the ascending() function
    selectionSort(array, 9, ascending);
    printArray(array, 9);

    return 0;
}

이때 argument로 넘겨줄 때 그냥 함수의 이름만으로 넘겨주고 있다
그래서 조사식으로 살펴보았다

함수 포인터에는 descending와 &descending와는 전혀 다른 값이 들어가 있다
즉 dereference를 통해서 자동으로 하는 것 같다
참고로 &descending와 descending가 똑같은 값을 가지고 있다는 것이다
즉 ampersand가 붙으나 마나 알아서 함수포인터에 넘겨줄 값을 처리한다는 것!!
헷갈리지만 알아두면 좋겠다

따라서 callback fucntion으로 넘겨줄 때도 descending, &descending 똑같이 작동한다
기능상에 문제도 발생하지 않는다
그리고 전달 받은 함수에서 parameter는

원래 descending의 주소를 담아뒀던 함수포인터 test와 똑같은 값을 가진다
즉 알아서 다 관리하는 듯 하다

Providing default functions

앞서 함수의 파라미터로 사용하는 함수를 default로 설정할 수도 있다
따라서 특별히 함수포인터를 설정하지 않는 한 항상 default가 실행되는 것이다

// Default the sort to ascending sort
void selectionSort(int* array, int size, bool (*comparisonFcn)(int, int) = ascending);

std::function

std::function을 이용해 함수 포인터를 사용하는 방법이 따로 있다
추가적으로 알아보고 싶으면 링크를 타고가서 공부하면 된다

Type inference for function pointers

auto를 이용해서 함수포인터를 활용할 수도 있다

#include <iostream>

int foo(int x)
{
	return x;
}

int main()
{
	auto fcnPtr{ &foo };
	std::cout << fcnPtr(5) << '\n';

	return 0;
}

위에서 보면 auto를 사용해서 함수포인터를 사용하고 있다
위에서 (*fcnPtr)(5)로 해도되고 fcnPtr(5)로 해도 derefer로 알아서 함수처럼 작동한다

Conclusion

함수 포인터는 너가 array에 함수를 담아두고 싶거나 다른 function에 argument로 pass하고 싶을 때 유용하게 사용할 수 있따

함수 포인터를 선언하는 기본 구문은 보기 흉하고 오류가 발생하기 쉬우므로 std::function을 사용하는 것이 좋습니다.

이를 종합적으로 사용해 계산기를 만든 예시 코드 입니다

#include <iostream>
#include <functional>

int getInteger()
{
    std::cout << "Enter an integer: ";
    int x{};
    std::cin >> x;
    return x;
}

char getOperation()
{
    char op{};

    do
    {
        std::cout << "Enter an operation ('+', '-', '*', '/'): ";
        std::cin >> op;
    }
    while (op!='+' && op!='-' && op!='*' && op!='/');

    return op;
}

int add(int x, int y)
{
    return x + y;
}

int subtract(int x, int y)
{
    return x - y;
}

int multiply(int x, int y)
{
    return x * y;
}

int division(int x, int y)
{
    return x / y;
}

using ArithmeticFunction = std::function<int(int, int)>;

ArithmeticFunction getArithmeticFunction(char op)
{
    switch (op)
    {
    case '+': return &add;
    case '-': return &subtract;
    case '*': return &multiply;
    case '/': return &division;
    }

    return nullptr;
}

int main()
{
    int x{ getInteger() };
    char op{ getOperation() };
    int y{ getInteger() };

    ArithmeticFunction fcn{ getArithmeticFunction(op) };
    if (fcn)
        std::cout << x << ' ' << op << ' ' << y << " = " << fcn(x, y) << '\n';

    return 0;
}
profile
청룡동거주민

0개의 댓글