함수를 객체처럼 다루는 것을 함수형 프로그래밍 (functional programming) 이라고 한다. 자세한 내용은 더 공부해봐야 하지만, C++보다는 Java, Javascript 등 다른 언어에서 더 잘 사용된다고 한다. 하지만 C++에서도 함수를 객체(또는 변수) 처럼 사용할 수 있는 방법이 있다. 이번 포스트에서는 그 방법들에 대해 알아볼 것이다.
1. Function Pointer
이전 포스트에서 정리한 Function Pointer는 함수를 변수처럼 다룰 수 있는 방법 중 하나이다. 함수의 주소를 변수로 받아두거나, 다른 함수의 인자로 받아서 실행할 수도 있다.
2. Functor (Function Object, 함수 구조체)
Functor는 구조체를 함수처럼 사용하는 방법이다. 구조체를 선언한 뒤, operator()를 오버로딩하면 구조체 객체의 이름으로 함수처럼 실행할 수 있게 된다.
주의점은 operator() 자체가 함수의 이름이므로 함수의 prototype은 "반환타입 operator()(인자);" 형태라는 점이다.
#include <iostream>
struct Functor
{
void operator()() {
std::cout << "Functor called!" << std::endl;
}
};
int main()
{
Functor functor;
functor(); // Functor called!
return 0;
}
3. Lambda Function
람다 함수는 함수를 어떤 Scope 안에서 임시로 정의하여 사용할 수 있는 방법이다. 간단한 함수를 어떤 함수의 인자로 전달할 때 주로 사용하는 듯 하다.
문법이 조금 특이한데, [](){} 형태로 사용된다. [] 내부에서는 Lambda Function이 정의된 Scope 안에서 특정 변수를 가져오는 Capture 기능이 사용되고, ()는 다른 함수와 마찬가지로 Parameter type을 정의하는 부분이다.
Capture에 대해 더 설명이 필요할 듯 하다. Capture를 통해 같은 scope에서 다른 변수를 가져오는 방법은 2가지로 나뉜다 : (1) Capture by Value, (2) Capture by Reference. (1) Capture by Value는 값을 복사해서 가져오는 것이고, (2) Capture by Reference는 값을 참조해서 가져오는 것이다. 변수 명을 쓰면 그 변수만 복사해서 받아오고, =을 쓰면 scope 안의 모든 변수 중 함수 정의 부분에서 사용되는 변수들을 찾아서 복사해서 받아온다. 변수명 앞에 &을 붙이면 그 변수를 참조해서 받아오고, &을 붙이면 함수 안에서 사용되는 변수들을 찾아서 참조로 받아온다. Capture by Value로 정의된 람다 함수는 값을 복사해서 받기 때문에 한 번 정의된 이후 외부에서 해당 변수에 접근할 수 있는 방법이 없어진다. 이러한 부분에서 Functional Programming의 의의에 가장 가까운 기능이라고 할 수 있을 것 같다. Capture by Reference로 정의된 람다 함수는 나중에 해당 변수를 변경하면 람다 함수 내부에서도 값이 변경된다.
람다 함수를 받을 때 타입은 type deduction (auto, template)을 이용하는 방법이 일반적이다. 직접 타입을 명시해서 받을 수는 없고, 굳이 하자면 functor로 선언하는 방법이 있겠다.
#include <iostream>
int main()
{
int a = 24;
// Capture by Value (copy)
auto func1 = [a](){ std::cout << a << std::endl; };
auto func2 = [=](){ std::cout << a << std::endl; };
// Capture by Reference
auto func3 = [&a](){ std::cout << a << std::endl; };
auto func4 = [&](){ std::cout << a << std::endl; };
a = 48;
func1(); // 24
func2(); // 24
func3(); // 48
func4(); // 48
return 0;
}
4. function (#include <functional>)
위에서 언급한 3가지를 동일한 타입으로 받는 방법이 존재하는데, <functional> 라이브러리의 function 템플릿을 사용하는 것이다. void (*pFunc)(void), Functor 객체, func1~func4를 모두 function<void(void)> 타입으로 받을 수 있다. 사용 방법은 function<반환타입(인자타입)> 이다.
#include <iostream>
#include <functional>
struct Functor
{
void operator()()
{
std::cout << "Functor called!" << std::endl;
}
};
void FunctionPtr()
{
std::cout << "Function Pointer called!" << std::endl;
}
void RunFunction(std::function<void(void)> _Func)
{
_Func();
}
int main()
{
int a = 24;
// Capture by Value (copy)
const auto& func1 = [a](){ std::cout << a << std::endl; };
const auto& func2 = [=](){ std::cout << a << std::endl; };
// Capture by Reference
const auto& func3 = [&a](){ std::cout << a << std::endl; };
const auto& func4 = [&](){ std::cout << a << std::endl; };
a = 48;
Functor functor;
RunFunction(FunctionPtr); // Function Pointer called!
RunFunction(functor); // Functor called!
RunFunction(func1); // 24
RunFunction(func2); // 24
RunFunction(func3); // 48
RunFunction(func4); // 48
return 0;
}
[C++] 함수 포인터 (0) | 2024.03.19 |
---|---|
[C++] 포인터 (0) | 2024.03.19 |
[C++] Struct (구조체) (0) | 2024.03.12 |
[C++] 변수의 종류와 메모리 영역 (0) | 2024.03.01 |
[C++] 자료형 (Data Type) (0) | 2024.02.29 |