언어/Basic C++

[Basic C++] #45_람다 표현식 활용, 제네릭 람다 표현식, 람다 캡처 표현식, 리턴 타입으로서 람다 표현식

Hardii2 2022. 7. 6. 22:15

 

[Basic C++] #45_람다 표현식 활용, 제네릭 람다 표현식, 람다 캡처 표현식, 리턴 타입으로서 람다 표현식

 

STL 알고리즘 중 "람다 표현식"에 대해 알아보겠습니다.

"전문가를 위한 C"의 17 항목, "STL 알고리즘 마스터하기"에 해당하는 내용입니다.

 


 

Overview

 

  1. STL 알고리즘과 람다 표현식
  2. 캡처 블록 내 초기화
  3. std::function
  4. 콜백 람다 표현식

 

#0. STL 알고리즘과 람다 표현식

1. 제네릭 알고리즘 + 람다 표현식

  • STL이 제공하는 제네릭 알고리즘의 Predicate(함수 객체)로 활용할 수 있습니다.

 

2. 예제

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
	vector<int> intV{ 11, 55, 101, 200 };
	vector<double> doubleV{ 11.1, 55.5, 200.2 };

	// 람다 표현식 == Predicate
	auto isGreaterThan100 = [](auto i) -> bool {return i > 100; };

	// find_if(구간의 첫 번째, 구간의 마지막 직후, Predicate)
	auto it1 = find_if(cbegin(intV), cend(intV), isGreaterThan100);

	// integer vector 
	if (it1 != cend(intV))
		cout <<  "Greater than 100 : " << *it1 << endl;

	cout << "\n";

	// double vector
	auto it2 = find_if(cbegin(doubleV), cend(doubleV), isGreaterThan100);

	if (it2 != cend(doubleV))
		cout << "Greater than 100: " << *it2 << endl;
}

 

* 결과

 

Details

 

  • 람다 표현식을 제네릭 알고리즘의 Predicate(함수 객체)로 활용하는 예제입니다.

 

#1. 캡처 블록 내 변수 초기화

1. 캡처 블록 내 변수 선언

  • "람다 표현식"의 캡처 블록 안에서 캡처된 변수는 어떤 종류의 표현식으로도 초기화될 수 있습니다.
  • 아래 코드를 먼저 살펴보겠습니다.

 

2. 예제 코드 1

#include <iostream>
using namespace std;

int main()
{
	double pi = 3.1415;
	auto lam = [myStr = "Pi is : ", pi]() { cout << myStr << pi << endl; };

	lam();
}

 

* 결과 화면

 

Details

 

  • 위 코드 예제의 "myStr"은 람다 표현식의 캡처 블록 내에서 초기화되어 활용되고 있습니다. 

 

3. 예제 코드 2

#include <iostream>
using namespace std;

int main()
{
	// unique_ptr : 복제 될 수 없는 스마트 포인터
	auto myUniquePtr = make_unique<double>(3.1415);
	auto lam = [p = move(myUniquePtr)](){cout << *p << endl; };

	lam();
}

 

* 결과 화면

 

Details

 

  • 람다 표현식의 캡처 블록 안에서 우리는 "std::move()"도 사용 가능합니다.
  • 위 코드 예제를 살펴보면, "unique_ptr"을 캡처 변수로 선택합니다.
  • 이때, "unique_ptr"의 성격상 값에 의한 복제가 불가능하기 때문에, "std::move()"를 통해 이동 시맨틱으로 변경해 주는 것을 확인할 수 있습니다.

 

#2. std::function

1. 개념

  • std::function은 호출 가능한 어떠한 유형의 객체도 홀드 혹은 감쌀 수 있는 템플릿 클래스입니다.

 

2. 장점

#include <iostream>
#include <functional>

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

int main() {

    // #1. 함수 포인터
    std::function<int(int, int)> func1 = add; // function pointer
    // #2-1. 람다 표현식
    std::function<int(int)> func2 = [](int x) { return x * x; }; // lambda function
    // #2-2. 람다 표현식
    std::function<double(double)> func3 = [](double x) { return x / 2.0; }; // lambda function
    // #3. 가변 인자 함수, variadic function
    std::function<int(int, ...)> func4 = printf;

    std::cout << func1(2, 3) << std::endl; // Output: 5
    std::cout << func2(4) << std::endl; // Output: 16
    std::cout << func3(7.0) << std::endl; // Output: 3.5
    f3("%d, %d %d\n", 1, 2, 3); // Output : "1 2 3"

    return 0;
}

 

Details

 

  • 먼저, std::function 템플릿 클래스는 동일한 시그니처를 갖는 서로 다른 유형의 객체들을(함수 포인터, 함수 객체, 람다 표현식, C 스타일의 가변 인자 함수) 모두 담을 수 있으며 기존의 호출 방식 혹은 인터페이스를 유지하므로, 유연합니다.
  • 유연함, 타입 안정성, 다형성, 그리고 콜백 함수 기능 등을 제공합니다.

 

3. 예제 코드

#include <iostream>
#include <functional>
using namespace std;

// function<int(void)> 타입의 함수 정의
function<int(void)> multiplyBy2Lambda(int x)
{
	// return[&]() {return 2 * x; }; == 에러 발생!
	return [x]() {return 2 * x; };
}

int main()
{
	function<int(void)> f1 = multiplyBy2Lambda(5);

	cout << f1() << endl;
}

 

* 결과 화면

 

  • 위 코드 예제는 "람다 표현식"을 함수의 리턴 타입으로 활용하기 위해 "std::function" 클래스를 활용했습니다. 
  • "f1" 변수는 "function <int(void)> 타입으로 "multiplyBy2 Lambda" 메서드의 결과 값을 받아서,
  • 호출 연산자를 통해 결과를 화면에 출력하고 있습니다.
  • * 주의 : 람다 표현식의 실행 시점은 함수가 반환 값을 리턴한 이후에 실행되며, 캡처 변수를 "참조형"으로 설정할 경우 실행되는 시점에 참조할 변수가 이미 존재하지 않기 때문에 에러를 발생시킬 수 있습니다! 

 

#3. 콜백 함수

1. 예제 코드

#include <iostream>
#include <vector>
#include <functional>
using namespace std;

// 람다 표현식을 파라미터로 활용하기위해 "std::function" 래퍼 클래스 활용
void testCallback(const vector<int>& vec, const function<bool(int)>& callbackLam)
{
	for (const auto& val : vec)
	{
		if (callbackLam(val) != false)
			break;
		else
			cout << val << " ";
	}

	cout << endl;
}

int main()
{
	vector<int> v{ 4, 5, 6, 7, 8, 9 };
	// testCallback 함수 호출
	testCallback(v, [](int i) { return i > 6; });
}

 

* 결과 화면 

 

Details

 

  • 앞서 설명했듯이, 람다 표현식을 std::function 유형의 객체에 저장하면, 콜백 함수로 활용할 수 있습니다.