[C++] for_each 함수 사용법

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 한 임의의 함수 객체를 저장할 수 있는 객체입니다.

이 객체에 관한 내용은 여기에서 볼 수 있습니다.

 

[C++] 함수 포인터를 확장한 std::function 사용법

std::functionC++ 표준 라이브러리에서 제공하는 function 객체는 callable이라고 불리는 모든 대상을 저장하고 실행하는 객체입니다.여기서 callable은 ()를 붙여서 호출할 수 있는 대상을 말합니다. 예를

codingembers.co.kr

 

다음은 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에 관한 내용은 여기에 정리해 두었습니다.

 

[C++] 범위 기반 for 루프 (Range-based for loop) 사용법

범위 기반 for범위 기반 for 루프는 배열이나 컨테이너 같이 데이터의 연속을 나타내는 객체의 모든 멤버를 순환하는 구문입니다.이 기능은 C++11부터 도입되었습니다. 선언 방식은 다음과 같습니

codingembers.co.kr

 

하지만, 범위 기반 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 구문보다 성능이 뛰어납니다.

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유