방프리
Effective STL 이해하지 못한 항목 - 1 본문
항목 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 |