[전문가를 위한 C++] 18장. 표준 라이브러리 알고리즘 마스터하기
Chapter 18. 표준 라이브러리 알고리즘 마스터하기
알고리즘 개요
std::function
- <functional> 헤더 파일에 정의된 std::function 템플릿을 이용하면 함수를 가리키는 타입, 함수 객체, 람다 표현식을 비롯하여 호출 가능한 모든 대상을 가리키는 타입을 생성할 수 있다.
-
std::function을 다형성 함수 래퍼라고도 부르며 함수 포인터로도 사용할 수 있고, 콜백을 구현하는 함수를 나타내는 매개변수로 사용할 수도 있다.
std::function<R(ArgType, ...)> // R : 리턴 타입 // ArgType : 콤마로 구분한 매개변수
-
std::function으로 함수 포인터를 구현하는 방법은 다음과 같다.
void func(int num, const string& str) { cout << "func(" << num << ", " << str << ")" << endl; } int main() { function<void(int, const string&)> f1 = func; // auto keyword를 사용할 수 있다. // auto f1 = func; f1(1, "test"); return 0; }
-
std::function 타입은 함수 포인터처럼 작동하기 때문에 표준 라이브러리 알고리즘에 인수로 전달할 수 있다. find_if() 알고리즘에 적용한 예는 다음과 같다.
bool isEven(int num) { return num % 2 = 0; } int main() { vector<int> vec{1,2,3,4,5,6,7,8,9}; function<bool(int)> fcn = isEven; auto result = find_if(cbegin(vec), cend(vec), fcn); if (result != cend(vec)) { cout << "First even number: " << *result << endl; } else { cout << "No Even number found." << endl; } return 0; }
-
람다 표현식을 process()함수의 std::function 매개변수의 값으로 지정할 수 있다.
void process(const vector<int>& vec, function<void(int)> f) { for (auto& i : vec) { f(i); } } void print(int num) { cout << num << " "; } int main() { vector<int> vec {0,1,2,3,4,5,6,7,8,9}; process(vec, print); cout << endl; int sum = 0; process(vec, [&sum](int num) { sum += num; }); cout << "sum = " << sum << endl; return 0; // 0,1,2,3,4,5,6,7,8,9 // sum = 45 }
-
콜백 매개변수로 std::function을 사용하지 않고, 다음과 같이 함수 템플릿으로 만들어도 된다.
template<typename F> void processTemplate(const vector<int>& vec, F f) { for (auto& i : vec) { f(i); } } // processTemplate()은 일반 함수 포인터와 람다 표현식을 모두 받을 수 있다.
람다 표현식
- 함수나 함수 객체를 정의하지 않고, 필요한 지점에서 곧바로 함수를 직접 만들어 쓸 수 있는 일종의 익명 함수.
- [] : 람다 선언자 (람다 소개자), 캡쳐 블록
-
{} : 람다 표현식 본문
auto BasicLambda = [] { cout << "Hello from Lambda" << endl; }; BasicLambda(); // 이렇게 호출할 수 있다.
-
캡쳐 블록
double data = 1.23; auto capturingLambda = [data] { cout << "Data=" << data << endl; }; // 람다 표현식은 자신이 속한 스코프에 있는 변수에 접근할 수 있다.
- 캡쳐한 변수는 람다 표현식 (컴파일러 입장에서는 이름없는 펑터)의 멤버로 복제된다. const 속성은 계속 이어 받는다.
- 펑터 마다 함수 호출 연산자 operator()가 구현되어 있는데 람다 표현식의 경우 const로 설정 된다.
-
const 속성을 이어 받은 캡쳐된 변수를 수정하려면 mutable로 람다 표현식을 지정하면 된다.
double data = 1.23; auto capturingLambda = [data]() mutable { data *= 2; cout << "Data = " << data << endl; }; // mutable을 지정할 때는 매개변수가 없어도 받드시 소괄호를 적어야 한다.
-
레퍼런스로 캡쳐하여 수정할 수도 있다.
double data = 1.23; auto capturingLambda = [&data] { data *= 2; };
-
람다 표현식이 속한 스코프의 변수를 모두 캡쳐할 수도 있다.
[-] // 모든 변수를 값으로 캡쳐 [&] // 모든 변수를 레퍼런스로 캡쳐
- 캡쳐 리스트를 지정하면 골라서 지정할 수 있다.
- [*this] : 현재 객체의 복제본을 캡쳐한다.
- 람다 캡쳐 표현식
- 람다 캡쳐 표현식은 사용할 캡쳐 변수를 초기화 할 수 있다.
-
스코프에 있는 변수 중 캡쳐하지 않았던 것을 람다 표현식에 가져오는 데 사용할 수 있다.
double pi = 3.1415; auto myLambda = [myCapture = "Pi : ", pi] { cout << myCapture << pi << endl; }; // myCapture란 변수를 "Pi :"로 초기화 하고, 람다 표현식과 같은 스코프에 있던 pi 변수를 캡쳐한다. // myCapture처럼 비레퍼런스 캡쳐 변수를 캡쳐 이니셜라이저로 초기화할 때는 복제 방식으로 생성된다.
- 람다 캡쳐 변수는 std::move()를 비롯한 모든 종류의 표현식으로 초기화할 수 있다. unique_ptr처럼 복제할 수 없고 이동만 가능한 객체를 다룰 때 이 점을 반드시 명심해야 한다.
- 기본적으로 값으로 캡쳐하면 복제 방식으로 적용된다.
- 따라서 unique_ptr을 람다 표현식에서 값으로 캡쳐할 수 없다.
-
하지만 람다 캡쳐 표현식을 사용하면 다음과 같이 이동 방식으로 복제할 수 있다.
auto myPtr = std::make_unique<double>(3.145); auto myLambda = [p = std::move(myPtr)] { cout << *p; };
-
- 람다 캡쳐 표현식은 사용할 캡쳐 변수를 초기화 할 수 있다.
- 람다 표현식을 리턴 타입으로 사용하기
-
std::function을 이용하면 함수가 람다 표현식을 리턴하게 만들 수 있다.
function<int(void)> multiplyBy2Lambda(int x) { return [x] { return 2 * x; }; } // 리턴 타입은 인수를 받지 않고 정수를 리턴하는 함수인 function<int(void)>다. // 아래와 같이 호출한다. function<int(void)> fn = multiplyBy2Lambda(5); cout << fn() << endl; /* auto fn = multiplyBy2Lambda(5); cout << fn() << endl; */ // 아래와 같이 더 세련되게 표현할 수 있다. auto multiplyBy2Lambda(int x) { return [x] { return 2 * x; }; }
-
리턴한 람다 표현식은 함수가 끝난 뒤에 사용된다. x를 [&x]로 캡쳐하면 multiplyBy2Lambda() 함수의 스코프는 존재하지 않기 때문에 x의 레퍼런스는 이상한 값을 가리키게 된다.
-
- 람다 표현식을 매개변수로 사용하기
- std::function 타입의 함수 매개변수는 람다 표현식을 인수로 받을 수 있다. (std::function 부분 다시 읽어보자)
- 표준 라이브러리 알고리즘 활용 예제
-
count_if
vector<int> vec {1,2,3,4,5,6,7,8,9}; int value = 3; int cnt = count_if(cbegin(vec), cend(vec), [value](int i) { return i > value; }]);
-
generate
vector<int> vec(10); int value = 1; generate(cbegin(vec), cend(vec), [&value] { value *= 2; return value; });
-
함수 객체
표준 라이브러리 심층 분석
- 반복자
- 불변형 순차 알고리즘 : 검색, 비교, 집계
- 가변형 순차 알고리즘 : 복사, 삭제, 순서 바꾸기
- 연산 알고리즘
- swap, exchange 알고리즘
- 분할 알고리즘
- 정렬 알고리즘
- 이진 탐색 알고리즘
- 집합 알고리즘
- 최대/최소 알고리즘
- 병렬 알고리즘
- 수치 처리 알고리즘