Advanced C++

[C++] 함수 객체 Functor, std::function, std::bind

로파이 2021. 2. 22. 11:52

Functor

호출 가능한 함수 기능을 가진 객체

보통 구조체나 클래스로 정의된다.

 

Functor 클래스

template<typename T>
class Functor
{
public:
	Functor(T _th) : th(_th) {}
	bool operator()(const T& val) const { return val >= th; }
private:
	T th;
};

클래스 예시 내용을 보면 일반적인 생성자와 멤버 함수 th를 가지고 있다.

"호출 가능"의 의미는 구조체나 클래스 내부에 다음과 같은 함수가 정의되어 있을 때, 호출 가능한 객체로 본다.

 

OutputType operator()(InputType in1, ...) { //... return value; }

 

호출 함수의 매개 변수 수에 따라 명칭이 있는데, 0개: 제너레이터 / 1개: 단항 함수/ 2개: 이항 함수 으로 불린다. 또한 예시와 같이 bool 타입을 반환하는 함수라면 Predicate 조건 함수라고 한다.

 

컨테이너를 정렬하기위해 많이 사용되는 Functor 클래스의 쉬운 예시로 Algorithm.h에 정의되어 있는 greator<T> 클래스와 less<T> 클래스가 있다. 두 클래스는 각각 내림차순과 오름차순 정렬을 위한 Predicate 함수라고 할 수 있다.

// STRUCT TEMPLATE greater
template <class _Ty = void>
struct greater {
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _FIRST_ARGUMENT_TYPE_NAME;
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _SECOND_ARGUMENT_TYPE_NAME;
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef bool _RESULT_TYPE_NAME;

    constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const {
        return _Left > _Right;
    }
};

// STRUCT TEMPLATE less
template <class _Ty = void>
struct less {
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _FIRST_ARGUMENT_TYPE_NAME;
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty _SECOND_ARGUMENT_TYPE_NAME;
    _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef bool _RESULT_TYPE_NAME;

    constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const {
        return _Left < _Right;
    }
};

구조체로 정의되어 있으며 operator() 호출로 두 개의 인자를 받아 bool 타입을 반환하는 조건 이항 함수이다. 조건을 판별하기 때문에 멤버 변수를 둘 필요가 없다.

똑같은 동작을 하는 클래스로 정의한다면, 다음과 같이 가능하다.

template<typename T>
class SortByAscendingOrder
{
public:
	bool operator()(const T& lhs, const T& rhs) const { return lhs < rhs; }
};

template<typename T>
class SortByDescendingOrder
{
public:
	bool operator()(const T& lhs, const T& rhs) const { return lhs > rhs; }
};

 

Functor는 호출 가능한 함수로 취급되기 때문에 객체 인스턴스롤 생성한다음 인스턴스를 함수처럼 사용하면 된다.

Functor<float> thresh_func(0.0f); // threshold = 0.0f

bool up_signal = thresh_func(4.3f); // true

 

사용 방법 예시

모든 호출 가능한 함수를 사용하는 함수에서 사용가능하다. 다음 std::trasform과 비슷한 동작을 하는 내가 정의한 "Functor"를 위한 함수를 다음과 같이 정의하고 결과를 확인할 수 있다.

template<typename IterInBegin, typename IterInEnd, typename IterOutBegin, class Functor>
void mytransform(IterInBegin iter1, IterInEnd iter1End, IterOutBegin iter2, Functor func)
{
	for (; iter1 != iter1End; iter1++, iter2++)
	{
		*iter2 = func(*iter1);
	}
}
int main()
{
	vector<float> signal = {-1.1f, -2.1f, -4.3f, -3.1f, 0.2f, 0.4f, 0.5f, 1.1f};

	Functor<float> thresh_func(0.0f);
	vector<bool> rectified_signal(signal.size());
	mytransform(signal.begin(), signal.end(), rectified_signal.begin(), thresh_func);
 }

Functor 클래스를 헤더파일에 모아두고 사용하기

내가 사용하고자 하는 Functor 클래스를 myAlgo.h라는 헤더 파일에 정의해두고 새로운 네임스페이스안에서 관리를 한다.

namespace myAlgo
{
	template<typename T>
	class AdderConstant
	{
	public:
		AdderConstant(T _val) : val(_val) {}
		T operator()(T& ref_val) { ref_val += val; }
	private:
		T val;
	};

	template<typename T>
	class Adder
	{
	public:
		T operator()(const T& val1, const T& val2) { return val1 + val2; }
	};

	// Subtract, Multiply, ... etc

	template<typename T>
	class DotOp
	{
	public:
		T operator()(typename const std::vector<T> v1, typename const std::vector<T> v2)
		{
			if (v1.size() != v2.size()) return 0;
			T val = 0;
			auto iter1 = v1.begin(); auto iter2 = v2.begin();
			for (; iter1 != v1.end(); iter1++, iter2++)
			{
				val += (*iter1) * (*iter2);
			}
			return val;
		}
	};
}

메인 프로그램에서 내가 사용 하고자하는 함수의 클래스를 적절히 선택하고 인스턴스화하여 사용할 수 있다.

int main()
{
	vector<float> signal2 = { -1.1f, -2.1f, -4.3f, -3.1f, 0.2f, 0.4f, 0.5f, 1.1f };
	vector<float> signal3 = { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f };

	myAlgo::DotOp<float> dot; // my algortihm header
	cout << "dot product of signal 2 and signal 3" << endl;
	cout << dot(signal2, signal3) << endl;

	return 0;
}

 

std::function 클래스

<functional> 헤더에 정의된 std::function 클래스는 모든 함수 포인터와 호출가능한 객체를 명시적으로 담아 둘 수 있다.

 

std::function<T>

템플릿 인자 T = 반환타입(매개변수 타입1, 타입2, ...)

ex) Functor 클래스 예시를 std::function으로 정의

 

function<bool(float)>

 

template<typename T>
class Functor
{
public:
	Functor(T _th) : th(_th) {}
	bool operator()(const T& val) const { return val >= th; }
private:
	T th;
};
int main()
{
	Functor<float> thresh_func(0.0f);
	function<bool(float)> thresh_func_wrapper = thresh_func;
}

 

멤버 함수를 std::function으로 만들기

클래스의 멤버 함수는 항상 호출 시 첫번재 인자가 this와 같은 자신의 인스턴스에 대한 주소가 전달되는데 이는 클래스 멤버 변수와 함수를 사용할 경우 참조하기 위해서 꼭 필요하다.

즉 클래스 인스턴스가 매개변수에 포함되어야 하는데, 복사가 일어나지 않도록 참조형으로 매개 변수를 정의한다.

 

function<반환값(클래스&, 매개변수1, 매개변수2,...)> 

 

만약 멤버함수가 상수형일 경우 인스턴스가 상수임을 보장해야하므로 다음과 같이 정의해야한다.

 

function<반환값(const 클래스&, 매개변수1, 매개변수2,...)> 

 

다음과 같이 어떤 문자열 c에 대해 'A','T','G','C'일 경우 정의된 코드 변환을 수행하는 mutate 함수를 가지고 있는 Functor를 정의해보자.

class GeneTransform
{
public:
	GeneTransform(char a, char t, char g, char c)
		:A(a), T(t), G(g), C(c) {}
	void mutate(char& c) const
	{
		switch (c)
		{
		case 'A':
			c = A;
			break;
		case 'T':
			c = T;
			break;
		case 'G':
			c = G;
			break;
		case 'C':
			c = C;
			break;
		}
	}
private:
	char A, T, G, C;
};

 

따라서 상수형 mutate 맴버 함수를 잡아놓는 std::function은 다음과 같다.

function<void(const GeneTransform&, char&)> mutate_wrapper = &GeneTransform::mutate;

위 function 객체를 이용하여 유전자 string을 mutate하는 코드는 다음과 같다.

string gene = "ATGCATGCATGC";
GeneTransform mt('C', 'G', 'T', 'A');

cout << "mutated before:" << gene.c_str() << endl;
for (auto it = gene.begin(); it != gene.end(); it++)
{
	mutate_wrapper(mt, *it);
}
cout << "mutated gene:  " << gene.c_str() << endl;

mutate_wrapper의 첫번째 인자는 참조해야하는 GeneTransform의 인스턴스가 들어간 것을 알 수 있다.

 

std::bind

std::function을 단독적으로 호출하기 위해 항상 참조해야하는 this와 같은 인스턴스를 전달해야한다. 혹은 함수의 매개변수 중 어떤 인자를 미리 결정해둘 때 사용한다. 그런 인스턴스나 변수를 bind 함수를 통해 항상 함수의 매개변수로 묶어 놓고 함수를 호출할 때는 나머지 필요한 인자만 전달하면 된다.

auto mutate = std::bind(mutate_wrapper, std::ref(mt), std::placeholders::_1);	

여기서 std::ref()는 GeneTransform 클래스의 인스턴스 mt를 참조형으로 만들어주면서 mutate 호출시 GeneTransform 인스턴스 매개변수에 대한 복사 생성이 일어나지 않게 한다. std::placeholders::_1은 mutate 함수를 호출시 사용된 매개 변수 중 첫번째 매개 변수를 사용하겠다는 의미이다. 예로 mutate(ch, 1,2,3,4,5) 등 첫번째 ch만 std::placeholders::_1로 잡아내고 나머지는 무시된다. 

따라서 bind 함수는 mutate_wrapper의 매개 변수를 미리 지정해줄때 사용한다.

 

다음과 같이 인스턴스를 명시해주었기 때문에 mutate 함수 호출시 인스턴스를 전달할 필요가 없다.

auto mutate = std::bind(mutate_wrapper, std::ref(mt), std::placeholders::_1);	
cout << "mutated gene:     " << gene.c_str() << endl;
for (auto it = gene.begin(); it != gene.end(); it++)
{
	mutate(*it);
}
cout << "mutated reverse:  " << gene.c_str() << endl;