C++11에 추가된 람다와 function wrapper는 Cpp에서도 함수형 프로그래밍이 어느정도 가능하게 해줬다. (그럼에도 Cpp는 기본적으로 객체지향 프로그래밍 + performance에 최적화 되어있다고 보아야 한다. + DOP를 더한 정도?) 아무튼 함수를 object/variable 처럼 다루기 편해짐.
Function Object
함수형 프로그래밍의 시작은 함수를 변수처럼 다룰 수 있어야 한다는 것이다. 기존(C++11이전의) Cpp에서는 함수 오브젝트를 사용했는데 대충 다음과 같다.
#include <iostream>
using namespace std;
class Plus
{
public:
explicit Plus(int val) : local_val_{val} {}
int operator() (int x) const
{
return local_val_ + x;
}
private:
int local_val_;
};
int main()
{
Plus plus2{ 2 }; //2를 더하는 기능을 수행하는 함수 오브젝트
cout << plus2(10) << endl; //12를 출력
return 0;
}
위 코드를 보면 '2를 더하는 함수'를 plus2라는 변수로 다룬 것을 볼 수 있다. 이렇게 되면 우선 더할 값(local_val_)을 내부변수로 두어 접근을 못하게 할 수 있고, 추가로 plus2 함수 오브젝트를 콜백이나 higher order function으로 활용할 수 있다.
Lambda expression
람다는 아래의 lambda_plus2를 보면 된다.
#include <iostream>
using namespace std;
class Plus
{
public:
explicit Plus(int val) : local_val_{val} {}
int operator() (int x) const
{
return local_val_ + x;
}
private:
int local_val_;
};
int main()
{
Plus plus2{ 2 }; //2를 더하는 기능을 수행하는 함수 오브젝트
auto lambda_plus2 = [local_val_ = 2](int x)//2를 더하는 람다 함수
{
return local_val_ + x;
};
int a = plus2(10);
int b = lambda_plus2(10);
return 0;
}
많이 쓰이는건 [ captures ] ( params ) { body } 방식. 자세한건 cppreference로 ...
아무튼 주목할 점은 plus2 와 lambda_plus2의 어셈블리 코드가 정확히 같다는 것. Compiler Explorer에가서 위 코드를 복붙해보면 아래와 같이 어셈블리가 정확히 일치함을 확인할 수 있다.
즉 함수 오브젝트나 람다나 사실은 그게 그거다.
참고로 람다 함수의 [ captures ] 부분은 by ref로도 , by value로도 넘겨줄 수 있다. 또 함수 내부에서 사용하면 자동으로 외부 변수들을 참조할 수 있는 &,= 도 있다. Cpp 버전마다 허용되는 범위가 다양하므로 cppreference 참조할것
(https://en.cppreference.com/w/cpp/language/lambda)
std::function
function은 함수, 람다, 함수 오브젝트, 함수 포인터등을 wrapper이다.
기존에 함수를 parameter로 넘기기 위해 함수 포인터를 사용하는 방법은 다음과 같다.
#include <iostream>
using namespace std;
void PrintNum(int num)
{
cout << num << endl;
}
void RunFunction(int i, void (* fnPtr)(int))
{
(*fnPtr)(i);
}
int main()
{
void(*fnPtr)(int); //함수 포인터 선언
fnPtr = PrintNum;
RunFunction(13, fnPtr);//함수 포인터를 매개변수로
//13출력
return 0;
}
그런데 함수 포인터도 넘기고 함수 오브젝트도 넘기고 람다식도 넘기고 싶다면 어떻게 해야할까? std::function을 사용하면 된다.
#include <functional>
#include <iostream>
using namespace std;
class PrintFunc
{
public:
void operator()(int x) const{cout << x << endl; }
};
void PrintNum(int num)
{
cout << num << endl;
}
void RunFunction(int i, const function<void(int)>& fn)
{
fn(i);
}
int main()
{
void(*fnPtr)(int); //함수 포인터 선언
fnPtr = PrintNum;
auto lambda_print = [](int num) {cout << num << endl; };
PrintFunc func_obj_print;
RunFunction(13, fnPtr); //함수 포인터
RunFunction(17, lambda_print); //람다
RunFunction(23, func_obj_print); //함수 오브젝트
RunFunction(29, PrintNum); //함수
//13, 17, 23, 29 출력
return 0;
}
위 코드를 보면 함수포인터, 람다, 함수오브젝트, 함수를 모두 매개변수로 넘길 수 있음을 확인할 수 있다.
또한 vector 따위의 컨테이너에 담을 수도 있다. (당연히 함수를 변수처럼 다룰 수 있는거니까)
#include <functional>
#include <iostream>
#include <vector>
using namespace std;
class PrintFunc
{
public:
void operator()(int x) const { cout << x << endl; }
};
void PrintNum(int num)
{
cout << num << endl;
}
void RunFunctions(vector < std::function<void(int)>> functions)
{
int i = 1;
for (const auto & fn : functions)
{
fn(i++);
}
}
int main()
{
void(*fnPtr)(int); //함수 포인터 선언
fnPtr = PrintNum;
auto lambda_print = [](int num) {cout << num << endl; };
PrintFunc func_obj_print;
vector<std::function<void(int)>> functions;
functions.emplace_back(fnPtr);
functions.emplace_back(lambda_print);
functions.emplace_back(func_obj_print);
functions.emplace_back(PrintNum);
RunFunctions(functions);
//1, 2, 3, 4 출력
return 0;
}
'언어 > C++' 카테고리의 다른 글
아토믹으로 Lock-Free 자료구조 만들기 (0) | 2022.07.10 |
---|---|
아토믹 (Atomic, cpp17) (1) | 2022.07.03 |
Cpp - 상속#2 (2) | 2021.07.22 |
Cpp - 상속#1 (0) | 2021.07.18 |
C++ (복사/이동) 생성자, 할당자, Rule of Three(Five) (0) | 2020.11.08 |