for_each 함수
for_each는 주어진 구간 내에 있는 원소들에 대하여 지정한 함수 객체를 적용하는 함수입니다.
이 함수를 사용하려면 다음의 헤더를 포함해야 합니다.
#include <algorithm>
함수의 정의는 다음과 같습니다.
template<class InputIterator, class Function>
Function for_each(
InputIterator first,
InputIterator last,
Function func);
여기서 func는 매개 변수가 하나인 함수 객체입니다.
이 함수는 종료 시, func의 복사복을 반환합니다.
아래는 람다 함수를 for_each 함수에 지정하여, 벡터의 값을 변경하는 예문입니다.
int main(){
vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int sum = 0;
// 람다 함수를 벡터의 원소들에 적용
for_each( vec.begin(), vec.end(), [&sum](int val){ sum +=val; });
cout << sum << endl;
}
그리고, std::function 객체를 사용해서, function 객체에 저장된 함수를 적용할 수도 있습니다.
#include <iostream>
#include <vector>
#include <algorithm> // for for_each
#include <functional> // for std::function
using namespace std;
void double_value(int& val){
val *= 2;
}
void half_value( int& val){
val /= 2;
}
void print_vector(vector<int>& vec, function<void(int&)>& f){
for_each( vec.begin(), vec.end(), f); // function 적용
for( int x : vec){ // 변경된 vec의 값들을 출력
cout << x << " ";
}
cout << endl;
}
int main(){
vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
function<void(int&)> f = double_value;
print_vector(vec, f);
f = half_value; // 함수 교체
print_vector(vec, f);
}
▼출력
2 4 6 8 10 12 14 16 18
1 2 3 4 5 6 7 8 9
std::function 객체는 callable 한 임의의 함수 객체를 저장할 수 있는 객체입니다.
이 객체에 관한 내용은 여기에서 볼 수 있습니다.
다음은 for_each에 전달된 함수 객체( function object )가 호출된 횟수를 벡터의 원소들에 곱하는 예문입니다.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct sequential_multiply{ // 호출된 횟수를 곱하는 함수
int m_cnt = 1;
void operator()(int& val){
val *= m_cnt++;
}
};
int main(){
vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
for_each( vec.begin(), vec.end(), sequential_multiply());
for( int x : vec){
cout << x << " ";
}
}
▼출력
1 4 9 16 25 36 49 64 81
이 예문은 첫 번째 원소에 1을 곱하고, 두 번째 원소에 2를 곱하고, 세 번째 원소에는 3을 곱하는 식으로 원소의 값을 바꿔나갑니다.
그런데, 이 예문이 이렇게 제대로 작동하는 이유는 for_each 함수가 정해진 구간의 원소들에게 sequential_multiply 함수 객체를 순차적으로, 그리고 오직 한 번만 적용하는 것을 보증하기 때문입니다.
그리고, 의외로 많은 알고리즘의 함수가 순차적인 적용을 보증하지 않습니다.
예를 들면, std::transform 함수는 주어진 함수 객체를 컨테이너의 원소들에게 순차적으로 적용하지 않습니다.
그렇기 때문에 sequential_multiply 같은 함수 객체를 사용할 수 없습니다.
for_each의 반환 함수 객체
위에서 for_each 함수는 종료 시, 입력된 함수 객체의 복사본을 반환한다고 했습니다.
이 복사본은 정확히 말하자면, 이동 연산을 통해 생성된 객체입니다.
따라서, 복사의 비용이 큰 함수 객체라고 하더라도, 전달 비용은 그리 크지 않습니다.
또한, 이러한 함수 객체의 상태 정보를 사용할 수 있기 때문에, for_each 연산 결과를 다양한 방식으로 처리할 수 있습니다.
아래의 ComputeInfo는 입력되는 원소들의 연산 결과를 담을 수 있는 함수 객체( function object )입니다.
이 객체가 담고 있는 정보를 for_each 함수 수행 중뿐만 아니라, 함수가 종료된 후에도 참조하거나, 처리할 수 있습니다.
template < typename T >
class ComputeInfo{
int m_cnt{0};
T m_sum{0};
public:
void operator()( T t){
m_sum += t;
m_cnt++;
}
int getCnt() const { return m_cnt;}
T getSum() const { return m_sum;}
double getMean() const {
return static_cast<double>(m_sum) / static_cast<double>(m_cnt);
}
};
int main(){
vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 반환 함수 객체 사용
auto retInfo = for_each( vec.begin(), vec.end(), ComputeInfo<int>());
cout << "element count: " << retInfo.getCnt() << endl;
cout << "sum : " << retInfo.getSum() << endl;
cout << "mean : " << retInfo.getMean() << endl;
}
이 예문에서는, 반환 함수 객체를 사용해서 입력된 컨테이너 원소의 개수와 총합, 그리고 평균값을 출력하는 방법을 보여줍니다.
범위 기반 for와의 차이점
for_each 함수와 범위 기반 for( range-base for ) 구문과 차이가 없는 것 아닌가? 하는 의문을 가질 수도 있습니다.
sequential_multiply f;
for( int x : vec){
f(x);
}
심지어, 코드도 더 짧아 보이기도 합니다.
범위 기반 for에 관한 내용은 여기에 정리해 두었습니다.
하지만, 범위 기반 for 보다 유리한 점이 몇 가지 있습니다.
첫 번째, 범위 기반 for 구문은 함수 객체에 입력될 변수와 타입을 명시해야 합니다.
그 과정에서 암시적인 변환이나 위 코드의 변수 x가 복사된 값이라는 사실을 잊어버리는 실수가 발생할 수도 있습니다.
실제로, 위에 사용한 코드는 벡터 원소의 값을 변경하지 못합니다. ( 눈치채셨나요? )
제대로 하려면 다음과 같아야 합니다.
sequential_multiply f;
for( int& x : vec){ // 혹은 auto&
f(x);
}
for_each는 이러한 문제에 자유롭습니다.
두 번째, for_each는 지정된 함수를 적용시킬 원소의 범위를 정할 수 있습니다.
int main(){
vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
vector<int>::iterator iter = vec.begin();
iter = next(next(iter));
for_each( iter, vec.end(), sequential_multiply()); // 함수를 적용한 구간 지정
for( int x : vec){
cout << x << " ";
}
}
▼출력
1 2 3 8 15 24 35 48 63
이 예문에서는 벡터의 세 번째 원소부터 값이 변경이 됩니다.
세 번째, C++ 표준 라이브러리의( standard library ) 다른 많은 알고리즘과 마찬가지로 빠른 처리 속도를 위해 병렬 연산을 수행할 수 있습니다.
그렇기 때문에, 많은 데이터에 대해 범위 기반 for 구문보다 성능이 뛰어납니다.
'C, C++ > 표준 라이브러리' 카테고리의 다른 글
[C++] weak_ptr에 대한 설명과 사용법 (0) | 2024.07.28 |
---|---|
[C++] shared_ptr에 대한 설명과 사용법 (0) | 2024.07.27 |
[C++] 함수 포인터를 확장한 std::function 사용법 (0) | 2024.07.26 |
[C++] 함수 객체( Function Object )와 operator() (0) | 2024.07.20 |
[C++] unique_ptr에 대한 설명과 사용법 (1) | 2024.07.18 |