[STL] 함수 객체(1)



함수 객체의 종류

STL의 함수 객체는 다음 두 가지로 분류할 수 있다.

  • 일반 함수 객체 : 특정 기능을 수행하는 함수 객체
    • 산술 연산 함수 객체 : 산술 연산 기능을 수행(plus, minus, multiplies, divides, modulus, negate)
    • 비교 연산 함수 객체 조건자 : 비교 조건자(equal_to, not_equal_to, less, greater, greater_equal, less_equal)
    • 논리 연산 함수 객체 조건자 : 논리 조건자(logical_and, logical_or, logical_not)

여기서 조건자는 bool형식을 반환하는 함수류이다.

  • 함수 어댑터 : 함수류를 인자로 받아 다른 함수 객체로 변환
    • 바인더(binder) : 이항 함수 객체를 단항 함수 객체로 변환(bind1st, bind2st) [이항 -> 단항]
    • 부정자(negator) : 함수 객체 조건자를 반대로 변환(not1, not2)
    • 함수 포인터 어댑터 : 함수 포인터를 STL이 요구하는 함수 객체로 변환(ptr_fun)
    • 멤버 함수 포인터 어댑터 : 멤버 함수 포인터를 STL이 요구하는 함수 객체로 변환(mem_fun,mem_fun_ref)

함수 객체, 함수, 함수 포인터 조건자로 조건을 판단하는 예제를 보자.

#include <iostream>
using namespace std;

struct LessFunctor		// 1. 함수 객체 조건자
{
	bool operator() (int left, int right) const
	{
		return left < right;
	}
};

bool LessFun(int left, int right)			// 2. 함수 조건자
{
	return left < right;
}

void main() 
{
	bool (*LessPtr)(int, int) = LessFun;		// 3. 함수 포인터 조건자
	LessFunctor lessFunctor;

	// 모두 bool형식을 변환
	// 1. 함수 객체로 10과 20을 비교
	cout << lessFunctor(10, 20) << endl;

	// 2. 함수로 10과 20을 비교
	cout << LessFun(10, 20) << endl;

	// 3.함수 포인터로 10과 20을 비교
	cout << LessPtr(10, 20);
}

image

조건자는 객체의 상태값을 변경할 수 없다는 요구조건을 만족해야한다. 그래서 함수 객체 조건자의 operator() 연산자 오버로딩 함수는 모두 const함수이다. 그 이유는 조건자를 사용하는 많은 알고리즘에서 조건자가 변경되지 않는다는 전제하에 조건자를 내부적으로 복사하며, 이때 조건자의 상태값이 변경되면 예기치 못한 동작이 발생하기 때문이다. 그래서 STL 조건자는 내부 상태가 변경되지 않는 bool 형식을 반환하는 함수 객체이다.

어댑터의 인자로 사용되는 함수 객체 요구사항

  • 단항 함수 객체는 반드시 argument_type(인자 형식), result_type(리턴 형식)이 정의돼 있어야 한다.
  • 이항 함수 객체는 반드시 first_argument_type(첫 번째 인자 형식), second_argument_type(두 번째 인자 형식), result_type(리턴 형식)이 정의돼 있어야 한다.

어댑터는 함수 객체를 다른 함수 샛체로 변환할 때 위 정의 형식을 이용해 변환을 수행한다. 이러한 형식 정의 쉽게 하기 위해서 STL은 기본 클래스 unary_function과 binary_function을 제공한다. 따라서 위 형식들을 직접 정의할 필요는 없으며, 함수 객체는 위 기본 클래스를 상속받아 만들면 된다.

기본 형식이 어떻게 사용되는지 앞에서 살펴본 transform() 알고리즘을 이용해 몇 가지 예제를 만들어보자.

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

template<typename T>
struct Plus
{
	T operator() (const T& left, const T& right) const
	{
		return left + right;
	}
};

void main() 
{
	vector<int> v1;
	v1.push_back(10);
	v1.push_back(20);
	v1.push_back(30);

	vector<int> v2;
	v2.push_back(1);
	v2.push_back(2);
	v2.push_back(3);

	vector<int> v3(3);
	// STL 조건자 plus<int>
	// transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), plus<int>());

	// 사용자 정의 조건자 Plus<int>
	transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), Plus<int>());

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

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

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

image

결과는 어렵지 않다. v1과 v2를 더하여 v3에 저장한 것이다. 하지만 v1과 v2를 더하지 않고 v1의 모든 원소에 100을 더하고 싶다. 사용자가 정의한 함수 객체 Plus<int>를 사용하려면 이항 함수 객체인 Plus<int>의 한 인자를 100으로 고정하고 다른 인자는 v1의 인자를 받으면 된다. 즉, 이항 함수 객체 Plus<int>를 단항 함수 객체로 변환하면 된다. 이때 어댑터 binder1st<int>를 사용할 수 있다. 예제를 보자.

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

template<typename T>
struct Plus
{
	T operator() (const T& left, const T& right) const
	{
		return left + right;
	}
};

void main() 
{
	vector<int> v1;
	v1.push_back(10);
	v1.push_back(20);
	v1.push_back(30);

	vector<int> v3(3);
	// STL 조건자 plus<int>
	transform(v1.begin(), v1.end(), v3.begin(),binder1st<plus<int>>(plus<int>(),100));

	// 사용자 정의 조건자 Plus<int>
	//transform(v1.begin(), v1.end(), v3.begin(), binder1st<Plus<int>>(Plus<int>(), 100));

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

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

image

여기서 둥요한건 STL plus<int> 함수자는 잘 실행되지만 사용자 Plus<int>는 엄청난 에러가 발생한다. 그 이유는 어댑터 binder1st<int>가 이항 함수자를 단항 함수자로 변환하기 위해 first_argument_type, second_argument_type, result_type 형식 정의를 필요로 하기 때문이다.

사용자가 만든 함수 객체고 어댑터 적용이 가능하도록 필요 형식을 정의한 예제를 봐보자.

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

template<typename T>
struct Plus
{
	typedef T first_argument_type;
	typedef T second_argument_type;
	typedef T result_type;

	T operator() (const T& left, const T& right) const
	{
		return left + right;
	}
};

void main() 
{
	vector<int> v1;
	v1.push_back(10);
	v1.push_back(20);
	v1.push_back(30);

	vector<int> v3(3);
	// STL 조건자 plus<int>
	// transform(v1.begin(), v1.end(), v3.begin(),binder1st<plus<int>>(plus<int>(),100));

	// 사용자 정의 조건자 Plus<int>
	transform(v1.begin(), v1.end(), v3.begin(), binder1st<Plus<int>>(Plus<int>(), 100));

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

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

image

이제 사용자 함수 객체도 어댑터 적용이 가능하다. 하지만 위와 같이 first_argument_type, second_argument_type, result_type를 정의할 수 있지만, 기본 클래스 unary_function, binary_function을 상속받아 구현하는데 더 쉽고 일반적이다. 다음 예제는 이항 함수자 객체가 어댑터 변환이 가능하게 기본 클래스 unary_function을 사용한 예제이다.

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

template<typename T>
struct Plus : public binary_function<T,T,T>
{
	T operator() (const T& left, const T& right) const
	{
		return left + right;
	}
};

void main() 
{
	vector<int> v1;
	v1.push_back(10);
	v1.push_back(20);
	v1.push_back(30);

	vector<int> v3(3);
	// STL 조건자 plus<int>
	// transform(v1.begin(), v1.end(), v3.begin(),binder1st<plus<int>>(plus<int>(),100));

	// 사용자 정의 조건자 Plus<int>
	transform(v1.begin(), v1.end(), v3.begin(), binder1st<Plus<int>>(Plus<int>(), 100));

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

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

결과는 같다.

이제 우리는 함수 객체를 구현할 때 단항 함수자는 기본 클래스 unary_function, 이항 함수자는 binary_function을 상속받아 만들면 함수자를 어댑터와 결합하여 사용할 수 있다.

산술 연산 함수 객체

STL은 다음과 같이 6개의 산술 연산 함수 객체를 제공합니다.

  • plus<T> : 이항 연산 함수자로 + 연산
  • minus<T> : 이항 연산 함수자로 - 연산
  • multiplies<T> : 이항 연산 함수자로 * 연산
  • divides<T> : 이항 연산 함수자로 / 연산
  • modulus<T> : 이항 연산 함수자로 % 연산
  • negate<T> : 단항 연산 함수자로 - 연산(부호 바꾸기)

아래 예제는 이름 있는 객체와 임시 객체로 STL plus<T>의 사용 예이다.

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

void main() 
{
	plus<int> oPlus;
	// 1. oPlus 객체로 10,20 더하기. 암묵적 호출
	cout << oPlus(10, 20) << endl;
	// 2. oPlus 객체로 10,20 더하기. 명시적 호출
	cout << oPlus.operator()(10, 20) << endl;

	// 3. 임시객체로 10,20 더하기. 암묵적 호출 (일반적인 사용법)
	cout << plus<int>()(10, 20) << endl;
	// 4. 임시객체로 10,20 더하기. 명시적 호출
	cout << plus<int>()(10, 20) << endl;
}

image

위와 같이 3번 방법이 일반적이다.

다음 예제는 plus<T> 함수자처럼 구현한 사용자 Plus<T> 함수자를 만들고. 이항 함수자 이므로 어댑터 적용이 가능하게 binary_function 기본 클래스를 상속받아 구현하는 예제이다.

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

template<typename T>
struct Plus : public binary_function<T, T, T>	// 어댑터 적용이 가능하게
{
	T operator()(const T& left, const T& right)const
	{
		return left + right;
	}
};

void main() 
{
	Plus<int> oPlus;
	// 1. oPlus 객체로 10,20 더하기. 암묵적 호출
	cout << oPlus(10, 20) << endl;
	// 2. oPlus 객체로 10,20 더하기. 명시적 호출
	cout << oPlus.operator()(10, 20) << endl;

	// 3. 임시객체로 10,20 더하기. 암묵적 호출 (일반적인 사용법)
	cout << Plus<int>()(10, 20) << endl;
	// 4. 임시객체로 10,20 더하기. 명시적 호출
	cout << Plus<int>()(10, 20) << endl;
}

image

결과나 구현 방법은 STL plus<T>와 동일하다.

이제 산술 연산 함수자가 알고리즘에 어떻게 사용되는지 알아보자.

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

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

	vector<int> v2;
	v2.push_back(1);
	v2.push_back(2);
	v2.push_back(3);
	v2.push_back(4);
	v2.push_back(5);

	vector<int> v3(5);

	transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), plus<int>());
	cout << "v3(v1+v2) : ";
	for (vector<int>::size_type i = 0; i < v3.size(); i++) {
		cout << v3[i] << " ";
	}
	cout << endl;

	transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), multiplies<int>());
	cout << "v3(v1*v2) : ";
	for (vector<int>::size_type i = 0; i < v3.size(); i++) {
		cout << v3[i] << " ";
	}
	cout << endl;

	transform(v1.begin(), v1.end(), v3.begin(), negate<int>());
	cout << "v3(-v1) : ";
	for (vector<int>::size_type i = 0; i < v3.size(); i++) {
		cout << v3[i] << " ";
	}
	cout << endl;

	adjacent_difference(v1.begin(), v1.end(), v3.begin(), minus<int>());
	cout << "v3(v1 인접 원소와의 차) : ";
	for (vector<int>::size_type i = 0; i < v3.size(); i++) {
		cout << v3[i] << " ";
	}
	cout << endl;

	partial_sum(v1.begin(), v1.end(), v3.begin(), multiplies<int>());
	cout << "v3(v1 원소의 곱 누적) : ";
	for (vector<int>::size_type i = 0; i < v3.size(); i++) {
		cout << v3[i] << " ";
	}
	cout << endl;

	cout << "v1 모든원소의 곱 : " << accumulate(v1.begin(), v1.end(), 1, multiplies<int>());
}

image

비교 연산 조건자

STL은 다음의 여섯 가지 비교 연산 함수 객체 조건자를 제공한다

  • equal_to<T> : 이항 조건자로 == 연산
  • not_equal_to<T> : 이항 조건자로 != 연산
  • less<T> : 이항 조건자로 < 연산
  • less_equal<T> : 이항 조건자로 <= 연산
  • greater<T> : 이항 조건자로 > 연산
  • greater_equal<T> : 이항 조건자로 >= 연산

아래 예제는 조건자 less<T> 의 사용 방법이다.

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

void main() 
{
	less<int> oLess;
	// 1. oLess 객체로 10, 20을 비교 true. 암묵적 호출
	cout << oLess(10, 20) << endl;
	// 2. oLess 객체로 10, 20을 비교 true. 명시적 호출
	cout << oLess.operator()(10, 20) << endl;

	// 3. 임시 객체로 10, 20을 비교 true. 암묵적 호출 (일반적인 사용법)
	cout << less<int>()(10, 20) << endl;
	// 2. 임시 객체로 10, 20을 비교 true. 명시적 호출
	cout <<less<int>().operator()(10, 20) << endl;
}

image

아래 예제는 조건자 less<T>처럼 구현한 사용자 Less<T> 조건자이다. 이항 조건자 이므로 어댑터 적용을 위해 binary_function을 상속받아 구현한 예제이다.

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

template<typename T>
struct Less : public binary_function<T,T,bool>
{
	bool operator()(const T& left, const T& right) const
	{
		return left < right;
	}
};

void main() 
{
	Less<int> oLess;
	// 1. oLess 객체로 10, 20을 비교 true. 암묵적 호출
	cout << oLess(10, 20) << endl;
	// 2. oLess 객체로 10, 20을 비교 true. 명시적 호출
	cout << oLess.operator()(10, 20) << endl;

	// 3. 임시 객체로 10, 20을 비교 true. 암묵적 호출 (일반적인 사용법)
	cout << Less<int>()(10, 20) << endl;
	// 2. 임시 객체로 10, 20을 비교 true. 명시적 호출
	cout << Less<int>().operator()(10, 20) << endl;
}

image

다음 예제는 STL 비교 연산 조건자를 사용하여 조건에 맞는 원소의 개수를 출력하는 count_if() 알고리즘 예제이다.

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

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 << "20보다 작은 원소의 개수 : " << count_if(v.begin(), v.end(),bind2nd<less<int>>(less<int>(), 20))<<endl;
	cout << "20보다 작거나 같은 원소의 개수 : " << count_if(v.begin(), v.end(), bind2nd<less_equal<int>>(less_equal<int>(), 20)) << endl;
	cout << "20보다 큰 원소의 개수 : " << count_if(v.begin(), v.end(), bind2nd<greater<int>>(greater<int>(), 20)) << endl;
	cout << "20보다 작거나 같은 원소의 개수 : " << count_if(v.begin(), v.end(), bind2nd<greater_equal<int>>(greater_equal<int>(), 20)) << endl;
	cout << "20과 같은 원소의 개수 : " << count_if(v.begin(), v.end(), bind2nd<equal_to<int>>(equal_to<int>(), 20)) << endl;
	cout << "20과 다른 원소의 개수 : " << count_if(v.begin(), v.end(), bind2nd<not_equal_to<int>>(not_equal_to<int>(), 20)) << endl;
}

image

논리 연산 조건자

STL은 다음과 같은 세 가지 논리 연산 함수 객체 조건자를 제공한다.

  • logical_and<T> : 이항 조건자로 && 연산
  • logical_or<T> : 이항 조건자로 연산
  • logical_not<T> : 이항 조건자로 ! 연산

논리 조건자는 일반적으로 T에 bool형식을 지정한다. 일반적으로 피연산자의 논리 조건을 비교하기 때문이다.

logical_and<T>의 STL버전의 출려과 사용자 구현을 보인 예제이다.

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

template<typename T>
struct Logical_and : public binary_function<T, T, bool>
{
	bool operator()(const T& left, const T& right) const
	{
		return left && right;
	}
};

void main() 
{
	int n = 30;
	logical_and<bool> oAnd;
	// 1. oAnd 객체로 10 < n <50인가? true. 암묵적 호출
	cout << oAnd(greater<int>()(n, 10), less<int>()(n, 50)) << endl;
	// 2. oAnd 객체로 10 < n <50인가? true. 명시적적 호출
	cout << oAnd.operator()(greater<int>()(n, 10), less<int>()(n, 50)) << endl;

	// 3. 임시 객체로 10 < n <50인가? true. 암묵적 호출(일반적인 방법)
	cout << logical_and<bool>()(greater<int>()(n, 10), less<int>()(n, 50)) << endl;
	// 4. 임시 객체로 10 < n <50인가? true. 명시적적 호출
	cout << oAnd.operator()(greater<int>()(n, 10), less<int>()(n, 50)) << endl;
}

image




© 2022. by KSC

Powered by sora