항목 38 : 함수자 클래스는 값으로 전달되도록(pass-by-value) 설계하자
C/C++의 함수 포인터는 값으로 전달된다. STL의 함수 객체는 함수 포인터를 본따서 만든것이기 때문에 마찬가지로 값으로 전달(복사)된다.
함수 객체가 반드시 값에 의한 전달로만 이뤄져야 하는건 아니지만 대체로 그렇다.
또한 함수 객체는 웬만하면 최대한 작게 만들고 단형성을 유지하는게 좋다. 가상함수를 가지지 말라는 것인데 그렇다고 다형성을 가진 함수 객체를 만들지 말라는 것은 아니다.
브릿지 패턴(또는 Pimpl idiom, Pimpl 관용구 라고도 함)을 사용하면 함수 객체를 작게 유지하면서 마치 가상 함수를 사용하는 듯한 함수 객체를 만들수도 있다.
template<typename T>
class BPFC : public unary_function<T, void> {
public:
virtual void operator()(const T& val) const; // 가상 함수라서 슬라이스 문제가 생길 수 있음
private:
Widget w; // 많은 데이터들
int x;
...
};
예를 들어 이런 함수자 클래스가 있다고 하자. 데이터도 많고 가상함수로 인한 슬라이스 문제도 발생할 위험이 있다.
template<typename T>
class BPFCImpl {
private:
virtual ~BPFCImpl();
virtual void operator()(const T& val) const;
private:
Widget w;
int x;
...
friend class BPFC<T>; // 접근 허용
};
template<typename T>
class BPFC : public std::unary_function<T, void> {
public:
void operator()(const T& val) const { pImpl->operator()(val); } // 가상 함수가 아니다
private:
BPFCImpl<T> *pImpl; // 크기도 작아졌다
};
인터페이스와 구현부를 분리함으로써 작고, 단형성을 가진 함수자 클래스가 되었다.
한가지 고려해야 할 점은 값에 의한 전달로 인해 복사가 일어나므로 복사 생성자에서 구현부 포인터를 적절히 처리해야 한다.
가장 쉬운 방법으로는 스마트 포인터로 만들어서 참조 횟수를 증가시키는 것이다.
항목 39 : 술어 구문은 순수 함수로 만들자
◾ 술어 구문(술어 함수) : bool을 반환하는 함수. STL에서 폭넓게 사용된다.
◾ 순수 함수 : 함수 내에서 값이 변하지 않는 함수
◾ 술어 구문 클래스 : operator()가 술어 구문인 함수자 클래스
술어 구문 함수는 반드시 순수 함수이어야 한다. 이유는 아래 코드를 보면서 설명한다.
class BadPredicate : public std::unary_function<Widget, bool> {
public:
BadPredicate() : timesCalled(0) {}
bool operator()(const Widget&) { return ++timesCalled == 3; } // 3회 호출시 true 반환
}
private:
size_t timesCalled;
};
std::vector<Widget> vw;
vw.erase(std::remove_if(vw.begin(), vw.end(), BadPredicate()), vw.end());
3회 호출 시 true를 반환하는 술어 구문 클래스이다. 문제는 true가 반환되어 요소가 삭제(이동)될때이다.
true가 반환되어 복사가 이루어지면 술어 구문 클래스의 기본 복사 생성자에 의해 timeCalled가 다시 0으로 초기화 되어 3회 호출하면 6번째 요소가 삭제가 되며 계속 반복된다.
template<typename FwdIterator, typename Predicate>
FwdIterator remove_if(FwdIterator begin, FwdIterator end, Predicate p) // 여기서 복사 1번
{
begin = std::find_if(begin, end, p); // 여기서 1번
if (begin == end) return begin;
else {
FwdIterator next = begin;
return remove_copy_if(++next, end, begin, p); // 여기서 1번
}
}
remove_if의 구현을 보면 술어 구문인 p가 최대 3번까지 값으로 전달(복사) 되는것을 확인할 수 있다.
물론 복사될때마다 timeCalled는 계속 0으로 초기화된다.
처음 의도한것은 3회 호출 시 딱 한번만 요소를 삭제하는것인데 결과적으로 순수 함수가 아니기 때문에 복사에 의해 의도치 않은 결과가 발생했다.
위와 같은 자기 모순에 빠지지 않기 위해서는 operator()를 const로 선언하면 된다.
다만 mutable같은 예외 경우가 있기 때문에 아예 순수 함수로 만드는것이 안전하다.
'도서 > Effective STL' 카테고리의 다른 글
STL 프로그래밍을 더 재미있게 해주는 팁 모음 (1) (0) | 2022.12.22 |
---|---|
함수자, 함수 객체, 함수, 기타 등등 (2) (0) | 2022.12.21 |
알고리즘(Algorithms) (2) (0) | 2022.12.20 |
알고리즘(Algorithms) (1) (0) | 2022.12.20 |
반복자(Iterators) (0) | 2022.12.20 |