std::for_each 사용법
std::for_each는 주어진 구간 내에 있는 원소들에 대하여 지정한 함수 객체를 적용하는 함수입니다.
이 함수를 사용하려면 다음의 헤더를 포함해야 합니다.
#include <algorithm>
함수의 정의는 다음과 같습니다.
template<class InputIterator, class Function>
Function for_each(
InputIterator first,
InputIterator last,
Function func);
여기서 func
는 매개 변수가 하나인 함수 객체입니다.
그리고, 이 함수는 종료 시, func
의 복사복을 반환합니다. ( 이것은 아래에서 다시 설명합니다. )
다음은 람다( lamda ) 함수를 std::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
객체를 사용해서, 이 객체에 저장된 함수를 for_each 함수에 적용할 수도 있습니다.
#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 한 임의의 함수 객체를 저장할 수 있는 객체입니다.
[C++] 호출 가능한 객체를 저장하는 std::function
std::function 객체C++ 표준 라이브러리에서 제공하는 std::function 객체는 C++의 호출 가능한 모든 객체들( callable )을 저장하고 실행하는 객체입니다. 그리고, 여기서 말하는 callable은 () 연산자를 호출
codingbonfire.tistory.com
위에서는 std::function
객체 f
에 double_value 함수를 저장했다가 half_value로 교체하는 코드를 보여줍니다.
● 다음은 for_each에 전달된 함수 객체( function object )가 호출된 횟수를 std::vector
의 원소들에 곱하는 예문입니다.
#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 };
// vec의 모든 원소에 함수 객체 적용
for_each( vec.begin(), vec.end(), sequential_multiply());
for( int x : vec){
cout << x << " ";
}
}
▼출력
1 4 9 16 25 36 49 64 81
이 예문은 첫 번째 원소에 sequential_multiply 함수 객체의 m_cnt
의 값 1
을 곱하고, 두 번째 원소에는 1
증가된 m_cnt
의 값 2
를 곱하고, 세 번째 원소에는 다시 증간된 m_cnt
값 3
을 곱하는 식으로 원소의 값을 바꿔나갑니다.
이것을 보면, for_each 함수에 전달된 함수 객체가, 지속적인 사용을 위해, 어딘가에 저장된다는 것을 쉽게 예상할 수 있습니다.
이 저장된 함수 객체는 어떻게 사용되는 걸까요?
for_each의 반환 함수 객체
위 std::for_each 함수의 정의를 얘기할 때, 이 함수는 종료 시 입력된 함수 객체의 복사본을 반환한다고 했습니다.
이 복사본은 정확히 말하자면, 이동 연산( move semantics )을 통해 생성된 함수 객체입니다.
그렇기 때문에, 복사의 비용이 큰 함수 객체라고 하더라도, 이동 전달 비용은 그리 크지 않기 때문에, 이렇게 객체를 반환하는 것은 함수의 성능에 악영향을 주지 않습니다.
또한, 이렇게 이동되어 저장된 함수 객체의 상태 정보가 초기값으로 재설정되지 않고 유지되기 때문에, std::for_each의 연산 결과를 다양한 방식으로 처리할 수 있습니다.
아래의 ComputeInfo
는 입력되는 원소들의 연산 결과를 담을 수 있는 함수 객체( function object )입니다.
[C++] 함수 객체( function object, functor ) : operator()를 구현한 타입
함수 객체( function Object ) 함수 객체( function object )는 operator() 함수를 구현한 타입( type )을 말합니다. C++ 표준 라이브러리에서는, 펑터( functor )라고도 불리는, 이 타입을 주로 컨테이너나 알고리즘
codingbonfire.tistory.com
이 객체가 담고 있는 정보를 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_each 함수에 전달된 ComputeInfo<int>
함수 객체는 저장되어, 필요할 때마다 ()
연산자가 호출되고, 그 결과 이 객체의 m_cnt
와 m_sum
변수의 값을 변경해 나갑니다.
그리고, for_each 함수가 종료될 때, 저장된 함수 객체를 반환하기 때문에, m_cnt
와 m_sum
의 제대로 된 최종값을 사용할 수 있게 되는 것입니다.
범위 기반 for와의 차이점
아래의 코드를 보면, "std::for_each 함수와 범위 기반 for( range-base for ) 구문과 별 차이가 없는 것 아닌가?" 하는 의문을 가질 수도 있습니다.
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 함수 사용
for_each( vec.begin(), vec.end(), sequential_multiply());
sequential_multiply f;
for( int x : vec){ // 범위 기반 for 구문
f(x);
}
}
심지어, 위의 경우 범위 기반 for 구문이 더 간결해 보이기도 합니다.
[C++] 객체의 모든 원소를 순환하는 범위 기반 for 구문
범위 기반 for ( range-based for )범위 기반 for 루프는, C++11부터 도입된, 배열이나 컨테이너 같이 데이터의 연속을 나타내는 객체의 모든 멤버를 순환하는 구문입니다. 선언 방식은 다음과 같습니다.f
codingbonfire.tistory.com
하지만, std::for_each 함수를 사용하는 것이, 범위 기반 for를 사용하는 것보다 유리한 점이 몇 가지 있습니다.
첫 번째, 범위 기반 for 구문은 키워드 for
다음에 함수 객체에 입력될 변수와 타입( 여기서는 int x
)을 다시 명시해야 합니다.
그 과정에서 위의 변수 x
가 복사된 객체라는 사실을 잊어버리는 실수가 발생할 수도 있습니다.
sequential_multiply f;
for( int x : vec ){ // vec의 원소에서 복사된 x
f(x);
}
실제로, 위에 사용한 코드는 컨테이너 vec
의 원소의 값을 변경하지 못합니다.
제대로 하려면 다음과 같아야 합니다.
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
이 예문에서는 vec
의 세 번째 원소부터 값이 변경이 됩니다.
세 번째, C++ 표준 라이브러리의( standard library ) 다른 많은 알고리즘과 마찬가지로, 병렬 처리를 위한 코드를 작성할 수 있습니다.
그렇기 때문에, 대량의 데이터에 대해 범위 기반 for 구문보다 뛰어난 성능을 발휘할 수 있습니다.
이 글과 관련 있는 글들
간단한 함수 객체를 정의하기 위한 람다 표현식( lamda expression )