C++ 중급 - Function Object

타입·2024년 2월 26일
0

C++ 공부

목록 보기
16/17

Function Object 개념

  • 함수 객체 (Function Object, Functor)
    • ()를 사용해서 호출 가능한 객체
  • 연산자 재정의와 함수 객체
    a가 사용자 정의 타입의 객체일 때
    • a+b : a.operator+(b)
    • a-b : a.operator-(b)
    • a() : a.operator()()
    • a(1,2) : a.operator()(1,2)
struct plus
{
	int operator()(int arg1, int arg2)
	{
		return arg1 + arg2;
	}
};

int main()
{
	plus p; // p는 객체
    
	// p를 함수 처럼 사용
	int n = p(1, 2); // p.operator()(1, 2)
	std::cout << n << std::endl; // 3
}
  • 함수 대신 함수 객체를 사용하는 이유
    • 상태를 가지는 함수
    • 특정 상황에서 함수 객체가 함수보다 빠름 (인라인 치환)
    • 모든 함수 객체는 자신만의 타입을 가짐
  • Function Object Coding Guide 1
    • 다양한 타입을 받을 수 있도록 템플릿으로 제작
    • 함수 객체를 const로 만드는 걸 대비하여 operator()는 const member function
    • constexpr (C++11)로 컴파일 타임에 인자 값을 알 수 있다면 성능 향상에 도움
    • [[nodiscard]] (C++17)로 return 값이 무시된다면 경고 발생
template<class T>
struct plus
{
	[[nodiscard]] constexpr 
	T operator()(const T& arg1, const T& arg2) const 
	{
		return arg1 + arg2;
	}
};
  • Function Object Coding Guide 2
    • noexcept
    • perfect forwarding & template specialization
    • is_transparent member type

상태를 가지는 함수

일반 함수는 동작은 있지만 상태가 없음
함수 객체는 동작(멤버 함수) 뿐 아니라 상태(멤버 데이터)도 가질 수 있음
멤버 함수 실행 중에 얻은 데이터는 멤버 데이터에 보관 가능

#include <bitset>
#include <random>

class URandom
{
	std::bitset<10> bs;
	bool recycle;
    
    std::mt19937 randgen{ std::random_device{}() };
    std::uniform_int_distribution<int> dist{0, 9};
public:
	URandom(bool recycle = false) : recycle(recycle)
	{
		bs.set();
	}

	int operator()()
	{
    	if (bs.none())
        {
        	if (recycle)
            	bs.set();
            else
            	return -1;
        }
    	int k = -1;
        while (!bs.test( k = dist(randgen) ));
        bs.reset(k);
        
        return k;
	}
};
URandom urand;

int main()
{
	for ( int i = 0; i < 15; i++)
		std::cout << urand() << ", ";

	std::cout << std::endl;
}

0~9까지의 랜덤수를 중복하지 않고 반환하는 코드
10개 다 꺼냈을 때 recycle이 true라면 초기화시키고 다시 랜덤 반환


Closure & Function Object

  • STL의 std::find 알고리즘
    • std::find
      주어진 구간에서 값을 검색
      3번째 인자로 검색할 값 전달
    • std::find_if
      주어진 구간에서 조건 검색
      3번째 인자로 단항 조건자 전달
  • Predicate (조건자)
    bool(또는 bool로 변환 가능한 타입)을 반환하는 함수 또는 함수 객체
    • 단항 조건자: 인자가 1개인 조건자
    • 이항 조건자: 인자가 2개인 조건자
  • Closure (클로저)
    scope 내의 변수를 바인딩 할 수 있는 일급 함수 객체(first-class object)
#include <vector>
#include <algorithm>

class F 
{
	int value;
public:
	F(int v) : value(v) {}

	bool operator()(int n) const 
	{
		return n % value == 0;
	}
};
int main()
{
	std::vector<int> v = { 1,2,6,7,8,3,4,5,9,10 };
	
	int k = 3;
//	F f(k);
	auto r1 = std::find_if(v.begin(), v.end(), F(k)); // 임시객체 전달

	std::cout << *r1 << std::endl;
}

일반 함수로는 불가능한 일을 함수 객체로 가능하게 함
함수 객체는 scope 내의 지역변수를 캡쳐할 수 있는 기능이 있음


함수객체와 인라인 치환

  • 인라인 함수 (Inline Function)
    컴파일 시에 함수 호출식을 함수의 기계어 코드로 치환
    컴파일 시간 동작하는 문법

  • 인라인 함수와 함수 포인터
    인라인 함수라도 함수 포인터에 주소를 담아서 사용하면 인라인 치환될 수 없음

int add1(int a, int b) 			{ return a + b; }
inline int add2(int a, int b) 	{ return a + b; }

int main()
{
	int ret1 = add1(1, 2); // 호출
	int ret2 = add2(1, 2); // 치환

	int(*f)(int, int) = &add2;
	f(1, 2); // 호출
}
  • 함수 포인터를 사용한 sort()의 비교 정책 교체
    • 장점
      사용자가 비교정책을 교체 가능
    • 단점
      느림! (치명적)
      비교 정책을 담은 함수가 인라인 치환될 수 없음
void sort(int* x, int sz, bool(*cmp)(int, int))
{
	for( int i = 0; i < sz-1; i++)
	{
		for ( int j = i + 1; j < sz; j++)
		{
		//	if ( x[i] > x[j] )

			if ( cmp(x[i], x[j]) )
				std::swap(x[i], x[j]);
		}
	}
}

inline bool cmp1(int a, int b) { return a < b;}
inline bool cmp2(int a, int b) { return a > b;}

int main()
{
	int x[10] = {1,3,5,7,9,2,4,6,8,10};

//	sort(x, 10, &cmp1);
	sort(x, 10, &cmp2);

	for(auto e : x)
		std::cout << e << ", ";
}
  • 함수와 함수 객체
    • 함수
      자신만의 타입이 없음
      signature(반환타입과 인자 모양)가 동일한 모든 함수는 같은 타입
    • 함수 객체
      자신만의 타입이 있음
      signature가 동일해도 모든 함수 객체는 다른 타입
#include <algorithm>

struct Less
{ 
	inline bool operator()(int a, int b) const { return a < b; } 
};

struct Greater
{
	inline bool operator()(int a, int b) const { return a > b; }
};

//void sort(int* x, int sz, bool(*cmp)(int, int) )
//void sort(int* x, int sz, Less cmp )
template<class T>
void sort(int* x, int sz, T cmp )
{
	for( int i = 0; i < sz-1; i++)
	{
		for ( int j = i + 1; j < sz; j++)
		{
			if ( cmp(x[i], x[j]) )
				std::swap(x[i], x[j]);
		}
	}
}

int main()
{
	int x[10] = {1,3,5,7,9,2,4,6,8,10};
	Less 	f1; f1(1, 2); sort(x, 10, f1);
	Greater f2; f2(1, 2); sort(x, 10, f2);
}

sort() 함수를 템플릿으로 만들어 비교 정책 변경 가능하며 인라인 치환도 가능!


인라인 치환성

  • sort 비교 정책으로 일반함수를 사용하면
    • 장점
      비교 정책을 교체해도 sort() 함수는 한 개
      코드 메모리 증가 안함
    • 단점
      비교 정책 함수가 인라인 치환될 수 없음, 느림
  • sort 비교 정책으로 함수 객체를 사용하면
    • 장점
      비교 정책이 인라인 치환됨, 빠름
    • 단점
      정책을 교체한 횟수 만큼의 sort() 함수가 생성
      코드 메모리 증가
  • SLT과 함수 객체
    STL에는 이미 만들어진 많은 함수 객체를 제공함 - <functional> 헤더
#include <algorithm>
#include <functional>

int main()
{
	int x[10] = {1,3,5,7,9,2,4,6,8,10};
	
	std::greater<int> f;
	std::sort(x, x+10, f);

	std::sort(x, x+10, std::greater<int>()); // 임시 객체
	std::sort(x, x+10, std::greater()); // C++17
	std::sort(x, x+10, std::greater{}); // 중괄호 표기법
}
profile
주니어 언리얼 프로그래머

0개의 댓글