ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • std::transform()에 대한 간단한 고찰
    개발/C·C++ 2024. 9. 4. 22:21

    C++ 문법에 대해 살펴보다가 재미있는 코드가 있어서 간단하게 고찰해보려고 한다.

    공식 문서에 구현 예제까지 나와있다.

    헤더는 <algorithm>

    template<class InputIt, class OutputIt, class UnaryOp>
    constexpr //< since C++20
    OutputIt transform(InputIt first1, InputIt last1,
                       OutputIt d_first, UnaryOp unary_op)
    {
        for (; first1 != last1; ++d_first, ++first1)
            *d_first = unary_op(*first1);
     
        return d_first;
    }
    template<class InputIt1, class InputIt2, 
             class OutputIt, class BinaryOp>
    constexpr //< since C++20
    OutputIt transform(InputIt1 first1, InputIt1 last1, InputIt2 first2,
                       OutputIt d_first, BinaryOp binary_op)
    {
        for (; first1 != last1; ++d_first, ++first1, ++first2)
            *d_first = binary_op(*first1, *first2);
     
        return d_first;
    }

     

    대략적인 흐름은 원본 데이터에 대한 시작 반복자, 끝 반복자로 범위를 지정해주고

    결과물을 저장할 자료구조(우리 예제에서는 vector)의 시작 반복자,

    그리고 데이터를 가공할 방법을 안내하는 콜백 함수를 넣어주는 형태다.

     

    앞서 잠시 언급했듯이 vector를 사용해 볼 건데,

    transform()에 넣을 함수 포인터는 멤버 함수에 대한 함수 포인터다.

    멤버 함수 포인터를 만드는 방법은 일반 함수와 조금 다르다.

    멤버 함수를 호출할 때 보통 "인스턴스." 혹은 "포인터 변수->"의 방식으로

    멤버 함수를 호출하고 필요한 인수를 넣는데 컴파일러가 추가하는 코드가 있다.

    바로 멤버 함수의 첫 번째 인자로 해당 인스턴스의 주소값을 넘기는 점이다.

    클래스 입장에서는 모든 멤버 함수 내부에 this가 생기는 거다.

    이런 일반 함수와의 차이 때문에 멤버 함수의 포인터를 생성할 때 이에 대해 명시를 해줘야 한다.

    다음을 보면 멤버 함수의 첫 번째 인수가 this임을 확실히 알 수 있다.

    class TestClass
    {
    public:
        int some_func(int a)
        {
            cout << "some_func -> " << a << endl;
    
            return 1;
        }
    };
    
    int main()
    {
        TestClass tc;
        // int(TestClass*, int)로 지정해서 f(&tc, 5)도 가능하다
        function<int(TestClass&, int)> f = &TestClass::some_func;
        f(tc, 5);
    }

     

    다시 transform으로 돌아와보자. 만들어 볼 것은 다음과 같다.

    1. 크기를 지정해서 만든 vector<int> 인스턴스를 5개 생성한다

    2. 이들 인스턴스 5개를 멤버로 가지고 있는 wrapper_vec을 만든다

    3. 새로운 vector<int> 인스턴스를 만들어 wrapper_vec의 각 멤버들의 사이즈를 값으로 저장한다

     

    1. 크기를 지정해서 vector<int> 5개 만들어보자.

    vector<int> a(1);
    vector<int> b(2);
    vector<int> c(3);
    vector<int> d(4);
    vector<int> e(5)

     

    2. 이 각각의 인스턴스를 멤버로 가지고 있는 wrapper_vec을 만든다.

    vector<vector<int>> vec_wrapper;
    vec_wrapper.push_back(a);
    vec_wrapper.push_back(b);
    vec_wrapper.push_back(c);
    vec_wrapper.push_back(d);
    vec_wrapper.push_back(e);

     

    3. vertor<int> size_vec을 만들어 wrapper_vec의 각 멤버의 사이즈를 값으로 저장한다

    이 때 멤버함수 포인터를 넘기거나 람다를 사용하는 방법 둘 다 가능하다.

    // 멤버함수 포인터
    vector<int> size_vec(5);
    function<size_t(vector<int>&)> functor_vector = &vector<int>::size;
    transform(vec_wrapper.begin(), vec_wrapper.end(), size_vec.begin(), functor_vector);
    
    // 람다
    transform(vec_wrapper.begin(), vec_wrapper.end(), size_vec.begin(), [](vector<int>& vec) {
    	return vec.size();
    });

     

    mem_fn() 함수를 사용하면 별도로 함수 포인터 변수를 만들지 않아도 된다.

    transform(vec_wrapper.begin(), vec_wrapper.end(), size_vec.begin(), mem_fn(&vector<int>::size));

     

    그리고 멤버 함수 포인터를 넘길 때 다음과 같이 설정하면,

    transform(vec_wrapper.begin(), vec_wrapper.end(), size_vec.begin(), &vector<int>::size);

     

    Visual Studio 2022에서 출력되는 에러 메시지는 다음과 같다.

    _Func는 최소 하나의 인수를 받아야 하는데 vector의 size() 함수는 인수를 받지 않는다.

    멤버 함수 일 때, 코드 수준의 시그니처에는 인수를 받지 않아도

    컴파일러가 해당 인스턴스의 주소를 첫 번째 인수로 추가한다.

    이런 이유로 람다로 표현하면 조금 더 직관적으로 느껴진다.

    functional 헤더를 추가하지 않아도 되는 장점이 있다.

    개인적으로는 람다가 코드 가독성에 더 도움을 주는 것 같다.

     

    다음은 예제 코드 전체.

    #include <iostream>
    #include <functional>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    int main()
    {
    	//// 벡터들의 각 크기로 벡터 만들기
    	vector<int> a(1);
    	vector<int> b(2);
    	vector<int> c(3);
    	vector<int> d(4);
    	vector<int> e(5);
    
    	vector<vector<int>> vec_wrapper;
    	vec_wrapper.push_back(a);
    	vec_wrapper.push_back(b);
    	vec_wrapper.push_back(c);
    	vec_wrapper.push_back(d);
    	vec_wrapper.push_back(e);
    
    	vector<int> size_vec(5);
    	function<size_t(vector<int>&)> functor_vector = &vector<int>::size;
    	transform(vec_wrapper.begin(), vec_wrapper.end(), size_vec.begin(), &vector<int>::size);
    	
    	//transform(vec_wrapper.begin(), vec_wrapper.end(), size_vec.begin(), [](vector<int>& vec) {
    	//	return vec.size();
    	//	});
    
    	// 함수 객체를 만들어서 반환해주는 mem_fn() 함수	
    	// transform(vec_wrapper.begin(), vec_wrapper.end(), size_vec.begin(), mem_fn(&vector<int>::size));
    
    	for (auto it = size_vec.begin(); it != size_vec.end(); ++it)
    	{
    		cout << "vector size: " << *it << endl;
    	}
    }

     

    댓글

Designed by Tistory.