방프리

Effective STL 이해하지 못한 항목 - 1 본문

C++/EffectiveSTL

Effective STL 이해하지 못한 항목 - 1

방프리 2020. 1. 18. 04:00

항목 46. 알고리즘의 매개 변수로는 함수 대신 함수 객체가 괜찮다.

 

- 프로그래머가 코딩을 할 때 추상화의 정도가 높아질수록 코드의 효율을 떨어지게 됩니다.

( 상속 받는 부모의 클래스의 양이 많아지기 때문인 것 같습니다.)

간단한 예로 double을 가진 클래스를 조작하는 코드는 double을 직접 조작하는 코드보다 느리다는 것을

알 수 있습니다.

하지만 STL 함수객체를 알고리즘에 넘기는 게 진짜 함수를 넘기는 것보다 빠릅니다. 

(말이 안되죠;; 방금 위에서는 간접 조작이 느리다고 해놓고선)

예로 double의 백터를 내림차순으로 정렬한다 코딩을 한다면 이렇게 할 것입니다.

 

vector<double> v;

... // 이외의 코드

sort(v.begin(), v.end(), greater<double>());

 

만약 위의 내용을 아시는 분이라면 (코딩을 하실 때 연산량을 고려하시는 분이라면 ) 진짜 함수를 사용하거나,

인라인 함수를 이용해 좀 더 빠르게 할 지 모릅니다. 다음 아래의 예제는 inline을 사용한 필자가 올린 예제 입니다.

inline bool doubleGreater(double d1, double d2)

{

return d1 > d2;

}

.... // 이외의 코드

sort(v.begin(), v.end(), doubleGreater);

 

두 가지 예제 코드 중 어느 것이 더 빠를가요? 바로 전자의 코드입니다. 필자의 말에 의하면 대략 50 ~ 160%의

연산 속도 차이를 보여주었다고 합니다.

왜 이렇게 될까요? 바로 인라이닝입니다(inlining) 함수 객체의 operator() 함수가 인라인으로 선언되어있고

함수의 몸체가 있으면, 대부분의 컴파일러는 호출된 알고리즘의 템플릿 인스턴화 과정에서 이 함수를 인라인으로

만듭니다.

즉 첫 번째 예제에서 컴파일러는 greate<double>::operator()가 인라인 함수이므로 sort를 인스턴스화 할 때

인라인 확장을 합니다(이 부분이 가장 키 포인트입니다.)

결국 sort는 함수 호출을 전혀 하지 않고 추가 최적화까지 받게 되죠 

그렇다면 후자의 코드의 경우엔 어떻게 동작할까요?

함수를 매개변수로 넘기려고 하면 컴파일러는 이 함수를 함수포인터로 바꾸어 버립니다.

실질적으로 넘겨지는 것은 포인터인 것이죠.

즉 컴파일러는 두 번째 코드를 이런 식으로 해석합니다.

void sort(vector<double>::iterator first, vector<double::iterator last, bool(*comp)(double, double));

comp는 함수 포인터이기 때문에 sort가 동작할 때마다 포인터를 계속 호출합니다.

이게 첫 번째 코드보다 느린 첫 번째 이유입니다. 

두번째 이유는 바로 포인터는 inline이 되지 않습니다!! (왜냐고 물으신다면 컴파일러를 제작한 회사에다가 따지시는게;;;)

이 이유 때문에 C++의 sort가 C의 qsort보다 더 빠릅니다.

함수 객체를 이용하라는 또 다른 이유는 바로 프로그램 컴파일 때문이기도 합니다. 예로 

 

set<string> s;

... // 이외의 코드

transform( s.begin(), s.end(), ostream_iterator<string::size_type>(cout, "\n"),

mem_fun_ref(&string::size));

 

이 코드는 문제점 있습니다. 바로 STL 플랫폼이 const 멤버 함수(string::size)를 처리하는데 버그를 가지고 있다는 것 입니다.

어떻게 해결해야 할까요? 바로 함수 객체를 사용하는 것입니다.

 

struct StringSize:

public unary_function<string, string::size_type> 

{

string::size_type operator()(const string& s) const

{

return s.size();

}

};

마지막으로 함수 객체는 미묘한 언어문제를 막아 줄 수 있습니다. 예를 들어 함수 템플릿의 인스턴스 이름이 함수의

이름과 같지 않은 경우입니다.

 

template<typename FPType>

FPType average(FPType val1, FPType val2)

{

return (val1 + val2) / 2;

}

template<typename InputIter1, typename InputIter2>

void writeAverages( InputIter1 begin1, InputIter1 end1, InputIter2 begin2, ostream& s)

{

transform(begin1, end1, begin2, ostream_iterator<typename iterator_traits<InputIter1>::value_type>(s, "\n"),

average<typename iterator_traits<InputIter1>::value_type>);

}

 

위의 코드에서는 average 부분에서 애매한 표현식의 컴파일러 에러가 발생합니다.

어떤 템플릿을 인스턴스화할 지 뚜렷하지 않기 때문이죠 결국 함수객체를 사용하는 방법밖에 없습니다.

 

template<typename FPType>

struct Average:

public binary_function<FPType, FPType, FPType>

{

FPType operator()(FPType val1, FPType val2) const

{

return average(va1, val2);

}

};

template<typename InputIter1, typename InputIter2>

void writeAverages( InputIter1 begin1, InputIter1 end1, InputIter2 begin2, ostream& s)

{

transform(begin1, end1, begin2, ostream_iterator<typename iterator_traits<InputIter1>::value_type>(s, "\n"),

Average<typename iterator_traits<InputIter1>::value_type>);

}

 

결국엔 알고리즘의 매개변수로 함수 객체를 사용하는 것을 추천합니다.

최적화 및 컴파일 환경에도 대처가 가능하기 때문이죠

'C++ > EffectiveSTL' 카테고리의 다른 글

Effective STL 이해하지 못한 항목 - 4  (0) 2020.01.19
Effective STL 이해하지 못한 항목 - 3  (0) 2020.01.19
Effective STL 이해하지 못한 항목 - 2  (0) 2020.01.19
Effective STL  (0) 2020.01.18
Comments