Functor; 펑터(함수 개체; Functional Object)함수처럼 호출할 수 있는 ()연산자를 제공하는 클래스
함수 개체와 일반 함수의 차이점은 새로운 함수 개체의 생성을 허용하면서, 함수 개체가 서로 또는
자기 자신에게 더 유연하게 적용 가능하다는 점이다.
함수 포인터를 이용한 유한차분(미분)
double fin_diff(double f(double), double x, double h){
return (f(x+h)-f(x))/h;
}
double sin_plus_cos(double x){
return sin(x)+cos(x);
}
int main(void){
cout<<fin_diff(sin_plus_cos, 1., 0.001)<<'\n';
cout<<fin_diff(sin_plus_cos, 0., 0.001)<<'\n';
return 0;
}
2차 도함수를 계산하고 싶은 경우, fin_diff는 인수로 호출하는 것이 좋다.
하지만, fin_diff는 세 개의 매개변수를 가지고 하나의 매개변수를 가지는 자체 함수 포인터 매개변수와 일치하지 않는다.
따라서 함수 포인터를 이용해 fin_diff를 인수로 호출할 수 없다.
펑터(함수개체)는 애플리케이션에게 operator()를 제공해 해당 개체를 함수처럼 호출한다.
struct sc_f{
double operator()(double x) const{
return sin(x)+cos(x);
}
};
펑터는 매개변수를 내부 상태로 유지할 수 있다.
class psc_f{
public:
psc_f(double alpha): alpha(alpha) {}
double operator()(double x) const{
return sin(alpha*(x))+cos(x);
}
private:
double alpha;
};
펑터는 일반적으로 _f 접미사를 이용해서 펑터임을 명시해서 펑터임을 명시해 준다.그리고 해당 개체에는 _o 접미사를 붙인다.
근사화된 도함수는 d_라는 접두사를 붙이고, 2차 도함수는 dd_를 붙이며,
고차 도함수에 대해서는 d3_ 과 같은 접두사를 사용한다.
함수와 같은 매개변수기본적으로 서로 다른 타입의 인수를 받아들이는 접근 방식에는 상속과 템플릿이 있다.
제네릭 접근 방식이 접근 가능성과 성능 면에서 더 우수함
template <typename F, typename T>
T inline fin_diff(F f, const T& x, const T& h){
return (f(x+h)-f(x))/h;
}
int main(void){
psc_f psc_o(1.0);
std::cout<<fin_diff(psc_o, 1., 0.001)<<std::endl;
std::cout<<fin_diff(psc_f(2.0), 1., 0.001)<<std::endl;
std::cout<<find_diff(sin_plus_cos, 0., 0.001)<<std::endl;
return 0;
}
첫 번째 fin_diff는 psc_f(1.0)(펑터 생성자)로 생성한 psc_o(펑터 객체)를 전달한다.
두 번째 fin_diff는 psc_f(2.0)를 통해 즉석으로 생성한 객체를 전달한다.
psc_f 펑터는 operator()를 정의해 주었기 때문에, 이전에 함수 포인터를 사용한 유한 차분과 동일하게 동작한다.
Functor와 일반 함수와 달리,
Functor는 클래스(혹은 구조체)로 정의되고 operator()를 정의하여 함수를 리턴해준다.
펑터 합성펑터를 합성함으로서, 이전에 함수 포인터를 사용한 유한 차분을 고차 차분에서 사용하지 못한점을
해결할 수 있다.
fin_diff() 함수는 매개변수가 세개인 반면, 인수(fin_diff의 첫 번째 인수)는 단항 함수를 필요로 한다.
함수와 간격 크기를 내부에 두는 단항 펑터를 정의함으로써 해결
template <typename F, typename T>
class derivative{
public:
derivative(const F& f, const T& h): f(f), h(h) {}
T operator()(const T& x) const{
return (f(x+h)-f(x))/h;
}
private:
const F& f;
T h;
};
원본 함수와 파생함수 모두 펑터에서 만들어진 단항 함수이다.
first order
using d_psc_f=derivative<psc_f, double>;
psc_f psc_o(1.);
d_psc_f d_psc_o(psc_o, 0.001);
cout<<"der. of sin(0)+cos(0) is "<<d_psc_o(0.0)<<'\n';
second order
using dd_psc_f=derivative<d_psc_f, double>;
dd_psc_f dd_psc_o(d_psc_o, 0.001);
cout<< "2nd der. of sin(0)+cos(0) is "<<dd_psc_o(0.0)<<'\n';
1계 도함수를 별도로 생성하지 않고,
2계 도함수 펑터 클래스 내의 생성자에서 이를 생성해서 사용할 수도 있다.
template <typename F, typename T>
class second_derivative{
public:
second_derivative(const F& f, const T& h): h(h), fp(f, h) {}
T operator()(const T& x) const{
return (fp(x+h)-fp(x))/h;
}
private:
T h;
derivative<F, T> fp;
};
f에서 f’’에 대한 개체를 바로 만들 수 있다.
second_derivative<psc_f, double> dd_psc_2_o(psc_f(1.0), 0.001);
재귀재귀를 사용하면, 고차 도함수를 반복적으로 탐색할 수 있다.
template <typename F, typename T, unsigned N>
class nth_derivative{
using prev_derivative=nth_derivative<F, T, N-1>;
public:
nth_derivative(const F& f, const T& h): h(h), fp(f, h) {}
T operator()(const T& x) const{
return (fp(x+h)-fp(x))/h;
}
private:
T h;
prev_derivative fp;
};
template <typename F, typename T>
class nth_derivative<F, T, 1>{
public:
nth_derivative(const F& f, const T& h): f(f), h(h) {}
T operator()(const T& x) const{
return (f(x+h)-f(x))/h;
}
private:
const F& f;
T h;
};
위의 코드에서 nth_derivative<F, T, 1> 특수화 템플릿은 이전에 정의해서 사용했던 derivative 클래스와 동일하다.
따라서 상속 해서 사용해도 무방하다.
template <typename F, typename T>
class nth_derivative<F, T, 1>: public derivative<F, T>{
using derivative<F, T>::derivative;
};
22계 도함수 계산
nth_derivative<psc_f, double ,22> d22_psc_o(psc_f(1.0), 0.00001);
테일러 급수에 의하면, 순방향 차이와 역방향 차이를 번갈아 사용하면,
O(h)에서 O(h^2)로 오차를 감소시킬 수 있다.
template <typename F, typename T, unsigned N>
class nth_derivative{
using prev_derivative=nth_derivative<F, T, N-1>;
public:
nth_derivative(const F& f, const T& h): h(h), fp(f, h) {}
T operator()(const T& x) const{
return N&1?(fp(x+h)-fp(x))/h:(fp(x)-fp(x-h))/h;
}
private:
T h;
prev_derivative fp;
};
템플릿 인수 N은 컴파일 타임에 알 수 있기 때문에
조건 N&1 또한 컴파일 타임에 알 수 있다.
위의 코드는 여전히 nth_derivative의 템플릿 첫번째 인수와 클래스 생성자 첫 번째 인수가 중복된다.
아래와 같이 생성자 인수를 취해서, 타입을 추론하는 생성 함수를 만들 수 있다.
template <typename F, typename T, unsigned N>
nth_derivative<F, T, N> make_nth_derivative(const F& f, const T& h){
return nth_derivative<F, T, N>(f, h);
}
auto d7_psc_o=make_nth_derivative<psc_f, double , 7>(psc_o, 0.00001);
하지만, 만약 템플릿 매개변수의 순서를 변경할 경우, 짧게 쓸 수 있다.
(N을 선언해야 F와 T를 추론할 수 있기 때문에, 값을 가장 앞에 둔다.)
template <unsigned N, typename F, typename T>
nth_derivative<F, T, N> make_nth_derivative(const F& f, const T& h){
return nth_derivative<F, T, N>(f, h);
}
auto d7_psc_o=make_nth_derivative<7>(psc_o, 0.00001);
추론되지 않은 매개변수는 매개변수 목록의 앞에 위치해야 한다.
제네릭 축소(Generic Reduction)accumulate()에서 템플릿 매개변수에 함수를 사용하여 제네릭 축소
template <typename Iter, typename T, typename BinaryFunction>
T accumulate(Iter it, Iter end, T init, BinaryFunction op){
for(; it!=end; ++i) init=op(init, *it);
return init;
}
template <typename T>
struct add{
T operator()(const T& x, const T& y) const{
return x+y;
}
};
struct times{
template <typename T>
T operator()(const T& x, const T& y) const { return x*y; }
};
위의 코드는 시퀀스의 모든 요소에서 BinaryFunction을 적용해 축소할 수 있다.
vector v={7.0, 9.0, 11.0};
double s=accumulate(v.begin(), v.end(), 0.0, add<double>{});
double p=accumulate(v.begin(), v.end(), 1.0, times{});