본문 바로가기

Information Technology/C++

[C++] 람다 함수와 std::function

이 글은 개인의 학습을 목적으로 정리한 글입니다. 이점 참고하고 읽어주세요;)


간단한 함수를 정의할 때, 정해진 함수의 포맷을 그대로 따르지 않고 나름의 약식으로 함수를 정의하는 걸 람다lambda 함수라고 합니다. 작성해야 할 코드의 양이 많을 때 아주 효율적으로 사용할 수 있어서 C++ 외에도 다양한 언어들에서 사용되고 있다고 합니다. 오늘은 이 람다 함수에 대해 알아보겠습니다.


	auto func = [](const int& i)-> void {cout << "Hello, World"; };
	func(1234);

람다 함수의 생성 및 사용 방법은 위와 같습니다. 처음 보기에는 굉장히 해괴망측?하거나 복잡해 보일 수 있지만, 하나하나 자세히 설명해보겠습니다.

 

우선 auto func는 함수의 이름과 리턴 타입입니다. 이 예제에서는 리턴 타입의 설정을 컴파일러에게 알아서 맡기기 위해 auto 자료형을 선언했고 함수의 이름은 func입니다. 뒤에서 설명하겠지만 람다 함수에서는 함수의 이름을 정하는 게 필수가 아닙니다.

 

다음으로 []는 lambda introducer입니다. 람다 함수 생성의 시발점 역할을 하고, 저 괄호 안에는 & 또는 =이 들어가는데 이는 뒤에서 따로 설명하겠습니다.

 

lambda introducer([]) 바로 뒤 괄호는 함수의 매개 변수를 입력하는 괄호입니다. 일반 함수 선언과 마찬가지로 함수에서 사용할 매개변수를 입력하면 됩니다. 그리고 -> 기호와 그 옆의 void는 리턴 타입을 의미합니다. 일반 함수의 선언에서는 함수 이름 앞에 리턴 타입을 선언하지만, 람다 함수에서는 무조건 매개변수 입력 후 -> 화살표 뒤에 함수의 리턴 타입을 선언합니다. 만약 리턴 타입이 예제와 같이 void라면 -> void 는 생략해도 상관없습니다. 그리고 그 위의 괄호({}) 안에는 일반 함수처럼 바디를 입력해주면 됩니다. 그리고 세미 콜론(;)으로 코드를 마무리하면 람다 함수 정의가 마무리됩니다.

밑에 줄에서 func(1234);를 실행시켜보면 일반 함수와 같이 정상적으로 함수가 작동되는 걸 볼 수 있습니다.


	{
		string name = "JACK JACK";
		[&]() {std::cout << name << endl; }();
	}

이번에는 람다 함수를 정의하고 사용할 때, 특정 스코프 내의 변수를 다루는 방법에 대해 알아보겠습니다. 람다 함수에서 서 다루는 외부 변수의 관리는 위에서 말한 lambda introducer([])에서 다루고 있습니다.

위의 예제와 같이 [] 안에 레퍼런스(&) 기호를 입력하면 람다 함수가 존재하는 스코프 내의 변수를 레퍼런스로 가져옵니다. 즉, 변수에 대한 복사 없이 같은 주소값의 변수를 그대로 가져와서 사용합니다.

예제와 반대로, [] 안에 등호(=)가 입력되면 람다 함수가 존재하는 스코프 내의 변수를 복사(copy)해서 가져옵니다. 메모리 상에 외부 변수를 새로 복사하여 람다 함수 내에서 사용하게 됩니다.

람다 함수에서 특별히 외부 변수를 사용하지 않는다면 [] 안에 아무 것도 채우지 않아도 상관없습니다.


	vector<int> v;
	v.push_back(1);
	v.push_back(2);

	for_each(v.begin(), v.end(), [](int val)-> void {cout << val << endl; });

람다 함수의 주 사용 방식은 위와 같습니다. 우선 int 자료형 벡터에 1과 2를 저장했습니다.

그리고 #include <algorithm>을 헤더에 추가해서 for_each 함수를 사용합니다.

for_each 함수는 첫 번째와 두 번째 매개변수로 컨테이너의 범위를 받고, 마지막 매개변수로 오는 함수의 매개변수로 컨테이너 범위 내의 값들을 매개변수로 전달해줍니다.

이 예제에서는 벡터 v의 시작과 끝을 처음 두 매개변수로 주고, 마지막 매개변수로 오는 함수를 람다 함수로 바로 정의했습니다. 이렇게 정의하면 람다 함수의 매개변수인 int val에 벡터 v의 처음부터 끝까지의 멤버들이 각각 인자로 전달되고 프로그램은 람다 함수의 정의대로 멤버들을 출력합니다.

프로그램을 실행시켜보면 위와 같이 1과 2가 차례로 출력되는 걸 확인할 수 있습니다.


이번에는 함수 포인터의 사용을 용이하게 해주는 std::funciton과 std::bind에 대해 알아보겠습니다.

	// 헤더에 #include <functional> 추가
    
    	auto func2 = [](int val) {cout << val << endl; };

	std::function<void(int)> func3 = func2;
	func3(123);

std::function의 기능은 새로운 함수에 기존에 존재하는 함수를 복사해서 붙여 넣는 기능이라고 생각하면 될 것 같습니다.

std::function은 템플리타이즈를 통해 새롭게 정의하려는 함수의 리턴 타입과 매개변수를 설정합니다. 리턴 타입이 먼저 오고 괄호 안에 매개 변수가 입력되는데, 이 예제에서는 func2 함수의 리턴 타입이 void, 매개변수가 int형 변수이니 <void(int)>로 func3을 정의했습니다.

	std::function<void(double)> func3 = func2;
	func3(123.5);

위와 같이 리턴 타입은 그대로 두고, 매개 변수를 double로 바꾸고 func3에 123.5의 값을 주고 실행하면 정상적으로 프로그램이 실행되는 걸 확인할 수 있습니다. 하지만 func2의 리턴 타입이 void이기 때문에 리턴 타입을 수정하면 에러가 발생합니다.

	std::function<void(void)> func4 = std::bind(func3, 456);
	func4();

이번에는 std::bind라는 기능을 사용했습니다. std::bind는 std::function을 통해 함수를 정의할 때, 특정 값이나 특정 변수에만 묶어(bind)서 기존의 함수를 가져오는 기능입니다.

위 예제를 보면 func4는 std::function을 이용해서 func3를 가져오지만, func3와 달리 매개변수도 void로 설정하고 있습니다. 대신 std::bind를 통해서 func3 함수에 456이라는 값이 들어가는 경우에 대해서만 func4를 정의하고 있습니다.

때문에 func4를 실행할 때도 func3와 달리 int형 데이터를 주지 않고 그냥 실행하면

위와 같이 정상적으로 456이 출력되는 걸 볼 수 있습니다.

'Information Technology > C++' 카테고리의 다른 글

[C++] 멀티 쓰레딩 기초  (0) 2020.01.01
[C++] C++17에서 여러 개의 리턴값 반환  (0) 2020.01.01
[C++] 파일의 임의 위치 접근하기  (0) 2019.12.28
[C++] 파일 입출력  (0) 2019.12.27
[C++] 정규 표현식  (0) 2019.12.27