함수 객체 functor 객체이지만, 함수처럼 사용하는 객체
아래는 벡터의 일부라고 가정. size 및 swap 모두 구현되어 있다고 가정.
template<typename cont, typename comp>
void bubble_sort(cont& _cont, comp& _comp)
{
for (int i = 0; i < _cont.size(); ++i) {
for (int j = i + 1; j < _cont.size(); j++) {
if (!comp(cont[i], cont[j]))
cont.swap(i, j);
}
}
}
struct comp1 {
bool operator() (int a, int b) { return a > b };
};
struct comp2 {
bool operator() (int a, int b) { return a < b; }
};위 !comp(cont[i], cont[j])
부분을 확인했을 때, copm1 comp2는 객체이지만, 함수와 같이 활용하고 있음.
단지, operator () 연산자 오버로딩을 통해서 함수를 호출하는 것처럼 보이게 할 뿐.
이것을 쓰는 이유는 무엇인가? 위의 template<typename cont, typename comp>과 같이
“어떻게 비교를 할 것인가?”
를 상황에 따라 정의하고 싶을 때, 함수 역할을 하는 객체를 함께 인자로 전달하여 비교의 방식을 바꿀 수 있게 된다. ex) bubble_sort(vector, comp1)을 하면 오름차, bubble_sort(vector,comp2)를 하면 내림차로 정렬.
람다 함수 위와 같이 함수 객체를 통해서 무언가를 커스터마이징하고자 하는데, 이럴 때마다 객체를 계속해서 생성해야만 하는 문제가 발생할 때 사용하기 좋음
그 대안으로 나온 것이 람다함수
람다 함수는 이름이 없는 함수 객체라고 보아도 무방하다.
ex) 홀수 확인하는 람다함수 [](int i)->bool{return i%2 ==1;}
`[CaptureList](받는 인자) → 리턴타입 {코드 본체}
또한 리턴타입을 생략할 수 있음 `[CaptureList](받는 인자){코드 본체}
컴파일러에서 알아서 코드 본체의 return문을 보고 타입을 추측해줌.
(대신 return이 여러개인데 다르면 안됨)
사용방법은
[](int i){return i%2 ==1;} 혹은
auto func = [](int i){return i%2 ==1;} 후에 func(4) 처럼 사용이 가능함.
CaptureList란,
람다 함수도 함수이기 때문에 일반적으로 자신 외부에서 선언된 변수들은 사용할 수 없다. 그렇지만 이 캡쳐목록(capture list)를 사용하면 가능하게 된다.
int num = 0;
[&num](int i){
if(num > 2){ return true}
else { return false;}
}이런 형태로!
&을 붙이면 그 레퍼런스를 가져온다는 것인데 붙이지 않게 되면 const형태의 복사본을 가져와서 함수 내부에서 값을 바꿀 수 없게 된다.
만약, class 내에서 람다를 만들고 싶다면??
우선, 위와 같이 멤버 변수를 캡쳐할 수는 없다. 그렇기에 [this]로 함수 객체의 포인터를 캡처하는 것이 가장 좋음 (당연히 this는 레퍼런스 캡처 &가 되지 않는다.)
추가 캡처
[=] : 외부의 모든 변수를 복사본으로 캡처(const 아님)
[=a] 외부에 정의 되어 있는 a를 복사하여 캡처
[this] 클래스 내부에 정의되어 있는 멤버 변수를 사용할 때
[&] : 외부의 모든 변수를 레퍼런스로 캡처
모두를 뜻하는 기호는 뒤에 들어갈 수 없고, 데이터 영역의 변수(static / 전역)는 사용할 수 없음.
[&num](int i){
if(num > 2){ return true}
else { return false;}
}() //<- 함수를 구성하고 이렇게 뒤에 호출 연산자를 붙이면 바로 호출이 된다! 그렇지만~ 함수 객체 같은 경우에는 C++이 모던 C++로 넘어오면서 람다 함수에게 중요도가 밀리게 되었다. 그와 비슷하게 std::function이라는 라이브러리가 나오게 되었는데 이것도 한번 알아보자.
우선 이란 단순하게 직역 그대로의 의미로 부를 수 있다. 또는 “호출할 수 있다”라는 의미이다.
C++은 (): 호출 연산자를 통해서 호출할 수 있는 것을 모두 이라고 정의한다.
자, 그래서 std::function이랑은 무슨 관계인가?라고 한다면 C에서는 함수들만 보관할 수 있는 객체 함수포인터 가 있었다.
C++의 std::function은 함수 뿐만이 아닌 모든 을 보관할 수 있는 객체라고 볼 수 있다.
int customFunc(){
return 1;
}
struct customStruct{
void operator ()(int i) { return i;}
}
clas customClass{
void operator() (int i){return i;}
}위와 같이 callable한 함수와 구조체, 클래스를 각각 만들었다면 아래와 같이 보관할 수 있다.
int main(){
std::function<int> f1 = customFunc;
std::function<void(int)> f2 = customStruct();
std::function<void(int)> f3 = customClass();
std::function f4 = []()->{ 3+ 4}; //람다도 함수니 당연하게 됨
}와 같이 함수 외에도 여려 callable을 저장할 수 있게 된다.
(매개 인자 같은 경우에는 꺽쇠 안에 넣어준다)
std::function<반환타입(매개인자)> 함수객체이름 = 함수
std::function에 멤버 함수를 넣고 싶어졌다면?
쉽지 않은 결정이다. 멤버 함수의 경우 this를 통해 자신을 호출한 객체를 알 수 있지만, std::function 안에서 사용할 경우에는 누가 자신을 호출한 객체인지 알기가 어렵기 때문이다!
이런 경우에는 우리가 자주 사용하던 ::를 통해 명시적으로 나타내줄 수 있다.
class MyClass{
public :
int inner_func(int i){
return i;
}
}int main(){
MyClass myObj;
std::function<int(MyClass&)> inner = MyClass&::inner_func();
inner(i);
}위와 같이 사용할 수 있다.
멤버 함수를 함수 객체로 만들자! mem_fn
int main(){
vector<int> vec1;
vector<int> vec2;
vector<int> vec3;
vector<int> vec4;
...각 벡터 안에 여러 원소를 포함했다고 가정.
vector<vector<int>> vecContainer; //위에 생성한 벡터를 모두 push_back 했다고 가정.
}위와 같은 코드를 만들고,
vector<int> size_vec(4) 라는 벡터 안에 vecContainer에 들어있는 벡터들이 각 size를 넣어두고 싶다면?
std::trandform(vecContainer.begin(),vecContainer.end(),size_vec.begin(),&vector<int>::size);위 함수는 본 주제와 관련 없지만 , 간단하게 설명하자면 첫번째 인자부터 두번째 인자까지 네번째 인자의 함수를 실행해서, 세번째 인자에 넣어줘! 라고 볼 수 있다.
즉, vecContainer를 순회하여, 각 벡터 안에 size()라는 멤버함수를 사용하고 그 결과값을 size_vec의 처음부터 넣어주는 함수다.
그런데 여기서 문제가 있다. 네번째 인자가 단순히 함수 객체라면 모르겠지만, 벡터의 멤버 함수라는 것! 그렇기에 해당 함수는 자신이 수행해야할 객체가 무엇인지 전달받지 않았기에 오류가 터져버린다!
해결 방법은 두가지다.
1. 위에서 배운 방법
std::function<size_t(vector<int>&)> sz_func = &vector::size()
이렇게 구성한 후에, 네번째 매개인자로 넣어주어도 문제 없다.
2. mem_fn을 사용
std::mem_fn(&vector<int>::size)
위 사항을 매개인자로 넣어주어도 된다.
결국 mem_fn은 1번과 같이 매번 함수객체를 만들어주어야 할때 가볍게 만드는 방법이다.
그러나, 자주 쓰이진 않는다.
왜냐하면 , [](const auto& v){return v.size()}와 같은 람다 함수를 넣어주는 것이 동일한 작업을 할 뿐만 아니라, mem_fn을 쓰려면 <functional>헤더를 포함해야 하기 때문에 비 효율적이다.
bind 함수 객체의 인자를 정해주자! bind
void add(int x, int y){x + y;}
auto add_with = std::bind(add, std::placeholder::_1, 2);
add_with(3); //3+2 진행
add_with(3,5); //3+5진행
즉 bind는,
내가 함수 객체를 만들 때 그 함수 객체의 인자를 함께 묶어줄 수 있고, 또 특정 인자는 고정시켜줄 수 있는 것이다. std::placeholder::_1은 함수 객체를 부를 때 사용한 1번째 매개인자를 뜻한다. 2,3,4…도 지정할 수 있고, 순서를 바꾸어도 관계 없이 각 placeholder의 순서에 대응되게 입력이 된다.