[STL] 함수 객체(2)



바인더

바인더는 이항 함수자를 단항 함수자로 바꾼다. STL은 다음 두 가지 바인더를 제공한다.

  • bind1st : 이항 함수자의 첫 번째 인자를 고정하여 단항 함수자로 변환
  • bind2nd : 이항 함수자의 두 번째 인자를 고정하여 단항 함수자로 변환

아래 예제는 이항 함수자의 첫 번째 인자를 고정하는 bind1st의 예제이다.

#include <iostream>
#include <functional>
using namespace std;

void main() 
{
	// less의 첫 인자를 10으로 고정한 단항 조건자 binder 생성
	binder1st<less<int>> binder = bind1st(less<int>(), 10);

	// binder는 첫 번째 인자를 10으로 고정한 less
	cout << binder(5) << ':' << less<int>()(10, 5) << endl;
	// 두 문장이 같다.
	cout << binder(10) << ':' << less<int>()(10, 10) << endl;
	// 두 문장이 같다.
	cout << binder(20) << ':' << less<int>()(10, 20) << endl;
	// 두 문장이 같다.
	cout << "======위와 같음======"<<endl;
	// 임시객체 사용
	cout<<bind1st(less<int>(), 10)(5) << ':' << less<int>()(10, 5) << endl;
	cout << bind1st(less<int>(), 10)(10) << ':' << less<int>()(10, 10) << endl;
	cout << bind1st(less<int>(), 10)(15) << ':' << less<int>()(10, 15) << endl;
}

image

bind1st()는 이항 함수자를 단항 함수자로 변환하는 함수이며 실제 단항 함수자 클래스는 binder1st이다.

아래 예제는 이항 함수자의 두 번째 인자를 고정하는 bind2nd 바인더 예제이다.

#include <iostream>
#include <functional>
using namespace std;

void main() 
{
	// less의 첫 인자를 10으로 고정한 단항 조건자 binder 생성
	binder2nd<less<int>> binder = bind2nd(less<int>(), 10);

	// binder는 두 번째 인자를 10으로 고정한 less
	cout << binder(5) << ':' << less<int>()(5, 10) << endl;
	// 두 문장이 같다.
	cout << binder(10) << ':' << less<int>()(10, 10) << endl;
	// 두 문장이 같다.
	cout << binder(20) << ':' << less<int>()(20, 10) << endl;
	// 두 문장이 같다.
	cout << "======위와 같음======"<<endl;
	// 임시객체 사용
	cout<<bind2nd(less<int>(), 10)(5) << ':' << less<int>()(5, 10) << endl;
	cout << bind2nd(less<int>(), 10)(10) << ':' << less<int>()(10, 10) << endl;
	cout << bind2nd(less<int>(), 10)(15) << ':' << less<int>()(20, 10) << endl;
}

image

bind2nd()는 이항 함수자를 단항 함수자로 변환하는 함수이며 실제 단항 함수자 클래스는 binder2nd입니다.


부정자

부정자는 조건자를 반대의 조건자로 변환한다. STL은 다음 두 가지의 부정자를 제공한다.

  • not1 : 단항 조건자를 반대의 조건자로 변환
  • not2 : 이항 조건자를 반대의 조건자로 변환

아래 예제는 이항 조건자를 반대의 조건자로 변환하는 not2 부정자 예제이다.

#include <iostream>
#include <functional>
using namespace std;

void main() 
{
	less<int> oLess;
	binary_negate<less<int>> negate = not2(less<int>());

	// 모두 같다
	cout << negate(5, 10) << ':' << not2(oLess)(5, 10) << ':' << not2(less<int>())(5, 10) << endl;
	cout << negate(10, 10) << ':' << not2(oLess)(10, 10) << ':' << not2(less<int>())(10, 10) << endl;
	cout << negate(20, 10) << ':' << not2(oLess)(20, 10) << ':' << not2(less<int>())(20, 10) << endl;
}

image

less의 반대인 이항 조건자 이므로 negate(5,10)은 5>=10 연산으로 false이다. 10>=10은 true, 20>=10도 true이다.binary_negate<>는 not2() 부정자로 변환된 반대 이항 조건자 클래스이다. 일반적으로 객체를 생성하지 않고 not2(less<int>())(5,10)등을 사용한다.

아래 예제는 이항 조건자를 바인더로 단항 조건자로 만들고 not1으로 반대의 단항 조건자로 변환하는 예제이다.

#include <iostream>
#include <functional>
using namespace std;

void main() 
{
	binder2nd<less<int>> binder = bind2nd(less<int>(), 10);
	unary_negate < binder2nd<less<int>>> negate = not1(binder);

	// 모두 같다.
	cout << negate(5) << ':' << not1(binder)(5) << ':' << not1(bind2nd(less<int>(), 10))(5) << endl;
	cout << negate(10) << ':' << not1(binder)(10) << ':' << not1(bind2nd(less<int>(), 10))(10) << endl;
	cout << negate(20) << ':' << not1(binder)(20) << ':' << not1(bind2nd(less<int>(), 10))(20) << endl;
}

image

위 예제에서 binder는 두 번째 인자가 10으로 고정된 단항 조건자로 바뀌고 부정자에 의해 반대 조건자가 된다. 즉, negate(5)는 5<10의 반대인 5>=10으로 false이다.


함수 포인터 어댑터

함수 포인터 어댑터는 일반 함수를 어댑터 적용이 가능한 함수 객체로 변환한다. STL은 ptr_fun()이라는 함수 포인터 어댑터를 제공한다.

아래 예제는 함수 포인터 어댑터가 왜 필요한지 보여주는 예제이다.

#include <iostream>
#include <algorithm>
#include <numeric>
#include <vector>
#include <functional>
using namespace std;

bool Pred(int n) {
	return 30 <= n && n <= 40;
}

void main() 
{
	vector<int> v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);
	v.push_back(50);

	cout << "v : ";
	for (vector<int>::size_type i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}
	cout << endl;

	cout << "30이상 40이하의 원소 개수 : " << count_if(v.begin(), v.end(), Pred) << endl;
	// 다음은 에러이다. not1은 어댑터로 함수 객체에 argument_type, result_type가
	// 정의돼 있어야함
	cout << "30이상 40이하가 아닌 원소 개수 : " << count_if(v.begin(), v.end(), not1(Pred)) << endl;
}

설명은 코드안에 있다.

아래 예제는 일반 함수에 ptr_fun() 어댑터를 사용하여 함수 객체로 변환하고 not1() 어댑터를 적용한 예제이다.

#include <iostream>
#include <algorithm>
#include <numeric>
#include <vector>
#include <functional>
using namespace std;

bool Pred(int n) {
	return 30 <= n && n <= 40;
}

void main() 
{
	vector<int> v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);
	v.push_back(50);

	cout << "v : ";
	for (vector<int>::size_type i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}
	cout << endl;

	cout << "30이상 40이하의 원소 개수 : " << count_if(v.begin(), v.end(), Pred) << endl;
	// 가능
	cout << "30이상 40이하가 아닌 원소 개수 : " << count_if(v.begin(), v.end(), not1(ptr_fun(Pred))) << endl;
}

image

이제 일반 함수 Pred는 ptr_fun() 어댑터로 argument_type과 result_type이 정의된 함수 객체로 변환된다.

아래 예제는 실제로 ptr_fun() 어댑터가 어떻게 구현되어 있는지 사용자 버전 Ptr_Func()를 구현하는 예제이다.

#include <iostream>
#include <algorithm>
#include< functional>
#include <vector>
using namespace std;

//////////////////////////////////// server ////////////////////////////////////
// 클래스 : ptr_fun()의 함수 객체 클래스. 일반 함수의 함수 객체 클래스
// 여러가지 기능을 제공할 수 있다. argument_type, result_type등...
template<typename RType,typename AType>
class Ptr_fun_class : public unary_function<AType, RType>
{
	RType(*pf)(AType);
public:
	Ptr_fun_class(RType(*_pf)(AType)) :pf(_pf) { }
	RType operator()(AType n) const
	{
		return pf(n);
	}
};

// 일반 함수를 함수 객체로 변환하는 ptr_fun() 함수
template<typename RType,typename AType>
Ptr_fun_class<RType, AType> Ptr_fun(RType(*pf)(AType)) 
{
	return Ptr_fun_class<RType, AType>(pf);
}

//////////////////// client ////////////////////
bool Pred(int n)
{
	return 30 <= n && n <= 40;
}
void main() 
{
	vector<int> v;
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);
	v.push_back(50);

	cout << "v : ";
	for (vector<int>::size_type i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}
	cout << endl;

	cout << "30이상 40이하의 원소 개수 : " << count_if(v.begin(), v.end(), Pred) << endl;
	// 가능
	cout << "30이상 40이하가 아닌 원소 개수 : " << count_if(v.begin(), v.end(), not1(Ptr_fun(Pred))) << endl;
}

image

여기서 중요한 내용은 두 가지 이다. 첫 번째는 ptr_fun_class 클래스가 일반 함수의 주소를 저장하는 함수 포인터 멤버 변수를 가진다는 것, 이 클래스는 unary_function 클래스를 상속받아 이 클래스의 객체는 어댑터가 적용이 가능하게 한다. 두 번째는 Ptr_fun() 함수로 일반 함수의 주소를 인자로 받아 ptr_fun_class 함수 객체를 생성하고 반환한다.


멤버 함수 포인터 어댑터

멤버 함수 포인터 어댑터는 멤버 함수를 함수 객체로 변환하여 알고리즘이 객체 원소의 멤버 함수를 호출할 수 있게 한다. STL은 다음 두 가지 멤버 함수 포인터 어댑터를 제공한다.

  • mem_fun_ref() : 객체로 멤버 함수를 호출한다.
  • mem_fun() : 객체의 주소로 멤버 함수를 호출한다.

mem_fun_ref()

아래 에제는 mem_fun_ref() 어댑터의 사용 예제이다.

#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
using namespace std;

class Point
{
	int x;
	int y;
public:
	explicit Point(int _x = 0, int _y = 0) :x(_x), y(_y) { }
	void Print() const { cout << x << ", " << y << endl; }
	int GetX() const { return x; }
	int GetY() const { return y; }
};

void main() 
{
	vector<Point> v;
	v.push_back(Point(1, 1));
	v.push_back(Point(2, 2));
	v.push_back(Point(3, 3));
	v.push_back(Point(4, 4));
	v.push_back(Point(5, 5));

	// 호출 불가능
	// for_each(v.begin(),v.end(),&Point::Print);
	// 호출 가능
	for_each(v.begin(), v.end(), mem_fun_ref(&Point::Print));
}

image

위 예제에서 for_each(v.begin(),v.end(),&Point::Print);가 호출 불가능한 이유는 for_each() 알고리즘이 함수와 Point객체를 같이 호출하기 때문이다. 멤버 함수는 Point객체.Print()처럼 호출 되어야 한다. 이와 같이 호출될 수 있게 함수 객체를 생성 해주는 어댑터가 mem_fun_ref이다.

아래 예제는 사용자 mem_fun_ref()를 구현한 예제이다.

#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
using namespace std;

////////////////////// server //////////////////////
// 객체의 멤버 함수를 호출할 수 있는 함수 객체 클래스
template<typename RType, typename CType>
class Mem_fun_ref_class : public unary_function<CType, RType>
{
	RType(CType::* pf) () const;
public:
	Mem_fun_ref_class(RType(CType::* _pf)()const) :pf(_pf) { }
	RType operator() (const CType& o) const
	{
		return (o.*pf)();
	}
};

// 어댑터 함수 : 멤버 함수의 주소를 저장하는 함수 객체를 반환
template<typename RType, typename CType>
Mem_fun_ref_class<RType, CType> Mem_fun_ref(RType(CType::* pf)() const)
{
	return Mem_fun_ref_class<RType, CType>(pf);
}

class Point
{
	int x;
	int y;
public:
	explicit Point(int _x = 0, int _y = 0) :x(_x), y(_y) { }
	void Print() const { cout << x << ", " << y << endl; }
	int GetX() const { return x; }
	int GetY() const { return y; }
};

void main() 
{
	vector<Point> v;
	v.push_back(Point(1, 1));
	v.push_back(Point(2, 2));
	v.push_back(Point(3, 3));
	v.push_back(Point(4, 4));
	v.push_back(Point(5, 5));

	for_each(v.begin(), v.end(), Mem_fun_ref(&Point::Print));
}

image

for_each() 알고리즘의 fun(*p)는 fun이 Mem_fun_ref_class 객체이므로 fun.operator()(*p)를 호출하며, *p가 Point 객체이므로 ((*p).*pf)()를 호출해 실제 (*p).Print()를 호출한다.

mem_fun()

for_each() 알고리즘의 원소가 객체가 아닌 객체의 주소라면 mem_fun_ref() 어댑터가 아니라 mem_fun() 어댑터를 사용한다. 이유는 객체로 멤버 함수 포인터를 호출할 때는 (o.*pf)()로 호출하지만, 객체의 주소로 멤버 함수 포인터를 호출할 때는 (p->*pf)()로 호출하기 때문이다.

아래 예제는 사용자 mem_fun()을 구현한 예제이다.

#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
using namespace std;

////////////////////// server //////////////////////
// 객체의 멤버 함수를 호출할 수 있는 함수 객체 클래스
template<typename RType, typename CType>
class Mem_fun_class : public unary_function<CType, RType>
{
	RType(CType::* pf) () const;
public:
	Mem_fun_class(RType(CType::* _pf)()const) :pf(_pf) { }
	RType operator() (const CType* p) const
	{
		return (p->*pf)();
	}
};

// 어댑터 함수 : 멤버 함수의 주소를 저장하는 함수 객체를 반환
template<typename RType, typename CType>
Mem_fun_class<RType, CType> Mem_fun(RType(CType::* pf)() const)
{
	return Mem_fun_class<RType, CType>(pf);
}

class Point
{
	int x;
	int y;
public:
	explicit Point(int _x = 0, int _y = 0) :x(_x), y(_y) { }
	void Print() const { cout << x << ", " << y << endl; }
	int GetX() const { return x; }
	int GetY() const { return y; }
};

void main() 
{
	vector<Point*> v;
	v.push_back(new Point(1, 1));
	v.push_back(new Point(2, 2));
	v.push_back(new Point(3, 3));
	v.push_back(new Point(4, 4));
	v.push_back(new Point(5, 5));

	// STL 버전
	for_each(v.begin(), v.end(), mem_fun(&Point::Print));
	// 사용자 버전
	for_each(v.begin(), v.end(), Mem_fun(&Point::Print));
	// 객체 delete 생략
}

image

출력 결과는 모두 같다. 여기서 달라진 부분은 순차열(v)에 저장된 원소가 객체가 아닌 객체의 주소이므로 Mem_fun_class의 operator() 함수의 매개변수가 Point의 포인터이며 Point의 멤버 함수를 호출하기 위해서

(p->*pf)()를 사용한다. for_each() 알고리즘의 fun(*p)는 fun이 Mem_fun_class 객체이므로 fun.operator()(*p)를 호출하며, *p가 Point 객체의 주소이므로 ((*p)->*pf)()를 호출해 실제 (*p)->Print()를 호출한다.




© 2022. by KSC

Powered by sora