언어/C++

Cpp 함수 (C++11 lambda, std::function)

tsyang 2021. 7. 31. 23:51

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