Effective C++/Effective STL
[Effective STL] 39. predicate는 순수 함수일 것
김디트
2025. 1. 7. 11:12
728x90
항목 39. 술어 구문은 순수 함수로 만들자
술어 구문(predicate)
- bool값을 반환하는 함수
- 판독의 기준이 되는 로직을 포함하는 구문이다.
순수 함수(pure function)
- 함수가 반환하는 값이 그 함수의 매개 변수에 종속되는 함수
- f(x, y)의 반환값은 x, y가 바뀔 때에만 변한다.
- 즉, 매개변수가 아닌 내부에 존재하는 데이터는 const여야 한다.
술어 구문 클래스(predicate class)
- operator()가 술어 구문인 함수 객체 클래스
술어 구문 클래스는 복사에 신경써야 한다.
- 함수 객체는 값으로 전달되기 때문이다.(항목 38 참조)
- 그리고 STL 알고리즘이 내부적으로 함수 객체의 사본을 따로 보관할 수도 있다.
- 그러므로 술어 구문 함수는 반드시 순수 함수여야 한다.
잘못 설계된 술어 구문 클래스
class BadPredicate
{
public:
BadPredicate() : timesCalled(0) {}
bool operator()(const Widget&)
{
return ++timesCalled == 3;
}
private:
size_t timesCalled; // 해당 값이 변경되며 순수 함수가 아니게 된다.
};
vector<Widget> vw;
...
// 3번째 요소만 삭제하고 싶다.
vw.erase(remove_if(vw.begin(), vw.end(), BadPredicate()), vw.end());
// 하지만 3번째 요소 뿐 아니라 6번째 요소도 삭제되어 버린다!
- 그 이유는 remove_if가 대체로 아래처럼 구현되기 때문이다.
template<typename FwdIterator, typename Predicate>
FwdIterator remove_if(FwdIterator begin, FwdIterator end, Predicate p)
{
// find_if : 주어진 범위 내에서 조건을 만족하는 첫 번째 요소 탐색
begin = find_if(begin, end, p); // timesCalled가 0인 p가 복사 전달된다.
if(begin == end)
{
return begin;
}
else
{
FwdIterator next = begin;
// remove_copy_if : 주어진 범위 내에서 조건을 만족하는 요소를 제외한 나머지 요소를 다른 범위로 복사
return remove_copy_if(++next, end, begin, p); // timesCalled가 0인 p가 다시 복사 전달된다.
}
}
술어 구문 함수를 순수 함수로 만드는 방법
- 술어 구문 클래스의 opeartor() 함수를 const 함수로 선언한다.
class BadPredicate
{
public:
bool operator()(const Widget&) const
{
return ++timesCalled == 3; // const 선언 되었으므로 에러가 발생한다!
}
...
};
- 하지만 이 방법만으로는, 아래 데이터에 접근하여 수정하는 것을 막을 수 없다.
- mutable로 선언된 멤버 데이터
- const가 아닌 지역 static 객체
- const가 아닌 클래스 static 객체
- 네임스페이스 스코프 안에 있는 const가 아닌 객체
- const가 아닌 전역 객체
- 술어 구문이 되는 모든 형태는 위 데이터에 접근하여 순수 함수가 되지 않을 여지가 있다.
bool anotherBadPrecdicate(const Widget&, const Widget&)
{
static int timesCalled = 0;
return ++timesCalled == 3; // 순수 함수가 아니다!!!!
}
- 람다의 경우 캡처를 사용하여 캡처까지 같이 복사되도록 하는 트릭을 사용할 수 있을 것이다.
- 이 경우 순수 함수가 아니지만 정상 동작한다.
int timesCalled = 0;
auto lambda = [×Called](const int&)
{
// timesCalled가 캡쳐되었다.
return ++timesCalled == 3;
};
vw.erase(remove_if(vw.begin(), vw.end(), lambda), vw.end()); // 정상 동작
728x90