언어/Basic C++

[Basic C++] #48_함수 객체 어댑터, 바인더, not1(), not2(), mem_fn()

Hardii2 2022. 7. 9. 19:25

[Basic C++] #48_함수 객체 어댑터, 바인더, not_1(), not_2(), mem_fn()

STL 알고리즘 중 "함수 객체 어댑터"에 대해 알아보겠습니다.

"전문가를 위한 C"의 17 항목, "STL 알고리즘 마스터하기"에 해당하는 내용입니다.

 

 


 

함수 객체 어댑터

 

C++에서 제공하는 함수 객체를 보다 다방면에서 활용하기 위해 우리는 "함수 객체 어댑터"를 사용합니다.

함수 객체가 주어진 인터페이스와 형태가 다를 경우, 우리는 함수 객체를 단독으로 사용할 수 없습니다.

예를 들면, find_if의 프레디킷 함수는 하나의 인자만 넘겨받지만, "less<>()" 비교 함수 객체는 두 개의 인자를 필요로 하기 때문에 우리는 이때 함수 객체 어댑터를 활용합니다.

 

코드를 통해 자세한 내용을 살펴보겠습니다. 

 

바인딩
#include <iostream>
#include <string>
#include <functional>
using namespace std;

void func(int num, const std::string& str)
{
	cout << num << " " << str << endl;
}

int main()
{
	std::string myStr  = "Hello";

	// std::placeholders::_1 : 묶어둘 함수 인자의 첫 번재 인자를 첫 번재 인자로 설정합니다.
	auto f = bind(func, std::placeholders::_1, myStr);
	f(16);

	// std::placeholders::_2 : 묶어둘 함수 인자의 두 번째 인자를 첫 번째 인자로 설정합니다.
	auto f1 = bind(func, std::placeholders::_2, std::placeholders::_1);
	f1("Test", 16);
}

"바인더"는 함수의 특정 파라미터를 특정 값으로 묶어둘 수 있습니다!

 

<functional>에 정의된 "std::bind()"는 함수의 특정 파라미터를 특정 값에 묶어둘 수 있게 해 줍니다.

위 예제 코드를 살펴보면, "f()" 메서드는 "bind()"를 통해 첫 번째 인자는 "std::placeholders::_1", 그리고 두 번째 인자는 앞서 선언한 "myStr"로 묶어둡니다.

 

이때, "std::placeholders::_1"은 "func()" 메서드의 첫 번째 인자가 "f()" 메서드의 첫 번째 인자로 위치합니다.

더불어, "std::placeholders::_2"는 "func()" 메서드의 두 번째 인자를 "f1()" 메서드의 첫 번째 인자로 위치합니다! 

 

+ 참조형 바인딩
#include <iostream>
#include <string>
#include <functional>
using namespace std;

void increment(int& i) { ++i; }

int main()
{
	int num = 0;

	auto incr = bind(increment, num);
	incr();

	cout << num;
}

위 코드의 경우 "num"의 결과 값은 0입니다!

왜냐하면, "num" 변수는 bind를 통해 넘겨지는 과정에서 "복제에 의한 전달"이 이루어지기 때문입니다.

 

이때, 사용할 수 있는 메서드는 "ref()" 혹은 "cref()"로 바인딩 과정에서 "참조에 의한 전달"을 수행합니다!

 

+ 오버로딩 함수의 경우
#include <iostream>
#include <string>
#include <functional>
using namespace std;

void func(double& num) { ++num; }
void func(int& num) { ++num; }

int main()
{
    int num = 0;
	
    // auto f = bind(func, num);	// 에러!!
    
    auto f = bind((void(*)(double&))func, ref(num));
    f();

    cout << num;
}

오버로딩된 함수들의 경우 "bind()"와 함께 사용하면 문제가 발생합니다!

따라서, 두 개의 오버로딩 함수들 중 어느 것을 사용할 것인지 명시적으로 지정해야 합니다!

 

위 코드를 살펴보면, "(void(*)(double&)) func"처럼 함수 타입을 명시적으로 지정하여 "bind()"와 함께 사용합니다!

 

코드 예제
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

int main()
{
	vector<int> v{ 1, 2, 3, 4, 5, 6 };

	auto it = find_if(cbegin(v), cend(v),
		bind(greater_equal<>(), placeholders::_1, 5));

	while (it != end(v))
	{
		cout << *it << " is greater than 5" << "\n";
		++it;
	}
}

* 결과 화면

위 코드 예제는 "find_if()" 메서드의 Predicate 함수로 "less<>()", 비교 함수 객체를 사용하는 예제입니다.

"less<>()" 비교 함수 객체는 두 개의 인자를 필요로 하지만, Predicate 함수는 한 개의 인자만 받도록 인터페이스 설정이 되어있죠!

 

따라서, 우리는 앞서 공부한 바인딩을 통해 vector 컨테이너를 순회하며 인자로 받을 항목을 "less<>()" 함수 객체의 첫 번째 인자로 위치시키고, 항목들과 비교할 두 번째 인자는 "100"으로 묶어둠으로써 인터페이스 불일치 문제를 해결하고 있습니다!

 

not1(), not2()
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

int main()
{
	vector<int> v{ 10, 20, 30, 100, 40 };

	// 람다 표현식으로 작성된  Predicate
	auto perfectScore = [](int i) {return i >= 100; };

	function<bool(int)> f = perfectScore;

	auto it = find_if(begin(v), end(v), not1(f));

	if (it != end(v))
		cout << *it << endl;

}

*결과 화면

"not1()"과 "not2()"는 부정 연산 어댑터입니다.

"not1()" 혹은 "not2()" 메서드는 단항 함수 혹은 이항 함수의 결과를 인자로 받아 결과 값을 반전시킵니다.

 

mem_fn(), 멤버 함수 호출
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <functional>
using namespace std;

int main()
{
	vector<string> str{"one", "", "three", "four"};

	auto it = find_if(cbegin(str), cend(str), mem_fn(&string::empty));

	if (it != end(str))
		cout << "Empty at: " << static_cast<int> (it - begin(str)) << endl;
	else
		cout << "No Empty String!" << endl;
}

STL 컨테이너에 객체를 담고 있을 경우 제네릭 알고리즘에 넘겨줄 콜백 함수 혹은 프레디킷으로 객체의 멤버 메서드를 이용하고 싶을 때가 있습니다.

이때, 우리는 클래스의 멤버 메서드를 "mem_fn()" 사용합니다!

 

* 주의: "&string::empty()"의 함수 지정자 부분, "&string::"을 생략할 수 없습니다!