-----------------
title : 씹어먹는 C ++ - <14. 함수를 객체로! (C++ std::function, std::mem_fn, std::bind)>
cat_title : 14. 함수를 객체로! (C++ std::function, std::mem_fn, std::bind)
path : /C++
publish_date : 2019-02-24
next_page : 269
chapter : 함수 객체
-----------------

이번 강좌에서는

* *Callable* 의 정의
* `std::function` 
* `std::mem_fn`
* `std::bind`

에 대해 다룹니다.

![](/img/ChewingCpplogo.png)

안녕하세요 여러분! 지난 강좌에서 배우신 스마트 포인터 삼형제 (`unique_ptr`, `shared_ptr`, `weak_ptr`) 들은 어떠셨나요? 스마트 포인터를 도입함으로써 C++ 에서 메모리 제어를 훨씬 더 쉽게 수행할 수 있습니다.

이번 강좌에서는 C++ 에서 호출 가능한 모든 것을 포괄해서 나타내는 *Callable* 에 대해서 알아보도록 하겠습니다. 이번 강좌는 다음 강좌에서 쓰레드를 배우기 전에 짧게 거쳐가는 징검다리라고 보시면 됩니다.

### Callable

*Callable* 이란, 이름 그대로 나타내듯이 호출(Call) 할 수 있는 모든 것을 의미합니다. 대표적인 예시로 함수를 들 수 있겠지요.

하지만 C++ 에서는 `()` 를 붙여서 호출할 수 있는 모든 것을 *Callable* 이라고 정의합니다. 예를 들어서

```cpp-formatted
#include <iostream>


struct S {
  void operator()(int a, int b) { std::cout << "a + b = " << a + b << std::endl; }
};

int main() {
  S some_obj;

  some_obj(3, 5);
}
```

성공적으로 컴파일 하였다면

```exec
a + b = 8
```

와 같이 나옵니다. 그렇다면 여기서 `same_obj` 는 함수 일까요? 아닙니다. 하지만 `same_obj` 클래스 S 의 객체이죠. 하지만, `same_obj` 는 마치 함수 처럼 `()` 를 이용해서 호출할 수 있습니다. (실제로는 `same_obj.operator()(3, 5)` 를 한 것이죠.)

또 다른 예시로 람다 함수를 생각해봅시다.

```cpp-formatted
#include <iostream>

int main() {
  auto f = [](int a, int b) { std::cout << "a + b = " << a + b << std::endl; };
  f(3, 5);
}
```

성공적으로 컴파일 하였다면

```exec
a + b = 8
```

와 같이 나옵니다. f 역시 일반적인 함수의 꼴을 하고 있지는 않지만, `()` 를 통해서 호출할 수 있기에 `Callable` 이라 할 수 있습니다.

### std::function

C++ 에서는 이러한 `Callable` 들을 객체의 형태로 보관할 수 있는 `std::function` 이라는 클래스를 제공합니다. C 에서의 함수 포인터는 진짜 함수들만 보관할 수 있는 객체라고 볼 수 있다면 이 `std::function` 의 경우 함수 뿐만이 아니라 모든 `Callable` 들을 보관할 수 있는 객체 입니다.

이 `std::function` 을 어떻게 사용할 수 있는지 아래의 예시를 통해 보겠습니다.

```cpp-formatted
#include <functional>
#include <iostream>
#include <string>

int some_func1(const std::string& a) {
  std::cout << "Func1 호출! " << a << std::endl;
  return 0;
}

struct S {
  void operator()(char c) { std::cout << "Func2 호출! " << c << std::endl; }
};

int main() {
  std::function<int(const std::string&)> f1 = some_func1;
  std::function<void(char)> f2 = S();
  std::function<void()> f3 = []() { std::cout << "Func3 호출! " << std::endl; };

  f1("hello");
  f2('c');
  f3();
}
```

성공적으로 컴파일 하였다면

```exec
Func1 호출! hello
Func2 호출! c
Func3 호출! 
```

와 같이 나옵니다.

```cpp-formatted
std::function<int(const string&)> f1 = some_func1;
std::function<void(char)> f2 = S();
std::function<void()> f3 = []() { std::cout << "Func3 호출! " << std::endl; };
```

일단 위와 같이 `function` 객체를 정의하는 부분부터 살펴봅시다. `function` 객체는 템플릿 인자로 전달 받을 함수의 타입을 갖게 됩니다. 여기서 함수의 타입이라 하면, 리턴값과 함수의 인자들을 말합니다.

따라서 예를 들어 `some_func1` 의 경우 `int` 를 리턴하며, 인자로 `const string&` 을 받기 때문에 위와 같이 `std::function<int(const string&)>` 의 형태로 정의 됩니다.

한편 `Functor` 인 클래스 `S` 의 객체의 경우 단순히 `S` 의 객체를 전달해도 이를 마치 함수 인양 받게 됩니다. `S` 의 경우 `operator()` 가 인자로 `char` 을 받고 리턴타입이 `void` 이므로 `std::function<void<char)>` 의 꼴로 표현할 수 있게 됩니다.

마지막으로 람다 함수의 경우 마찬가지로 리턴값이 없고 인자를 받지 않기 때문에 `std::function<void()>` 로 정의되겠지요.

이렇든 `std::function` 은 C++ 의 모든 `Callable` 을 마음대로 보관할 수 있는 유용한 객체 입니다. 만약에 함수 포인터로 이를 구현하려고 했다면 `Functor` 와 같은 경우를 성공적으로 보관할 수 없었겠지요.


### 멤버 함수를 가지는 std::function

앞서 `function` 은 일반적인 `Callable` 들을 쉽게 보관할 수 있었지만, 멤버 함수들의 경우 이야기가 조금 달라집니다. 왜냐하면, 멤버 함수 내에서 `this` 의 경우 자신을 호출한 객체를 의미하기 때문에, 만일 멤버 함수를 그냥 `function` 에 넣게 된다면 `this` 가 무엇인지 알 수 없는 문제가 발생하게 됩니다.

아래의 예시를 보실까요.

```cpp-formatted
#include <functional>
#include <iostream>
#include <string>

class A {
  int c;

 public:
  A(int c) : c(c) {}
  int some_func() { std::cout << "내부 데이터 : " << c << std::endl; }
};

int main() {
  A a(5);
  std::function<int()> f1 = a.some_func;
}
```

컴파일 한다면 아래와 같은 컴파일 오류가 나게 됩니다.

```compiler-warning
test2.cc: In function 'int main()':
test2.cc:17:26: error: invalid use of non-static member function 'int A::some_func()'
   std::function<int()> f1 = a.some_func;
                        ~~^~~~~~~~~
test2.cc:10:9: note: declared here
     int some_func() {
         ^~~~~~~~~
```

왜냐하면 `f1` 을 호출하였을 때, 함수의 입장에서 자신을 호출하는 객체가 무엇인지 알 길이 없기 때문에 `c` 를 참조 하였을 때 어떤 객체의 `c` 인지를 알 수 없겠지요. 따라서 이 경우 `f1` 에 `a` 에 관한 정보도 추가로 전달해야 합니다.

그렇다면 이를 어떻게 할까요? 사실 멤버 함수들은 구현 상 자신을 호출한 객체를 인자로 암묵적으로 받고 있었습니다.\sidenote{혹시라도 파이썬을 써보신 분들은 알겠지만, 멤버 함수들이 def func(self) 이런 식으로 정의되는 것과 마찬가지 입니다.}

따라서 이를 받는 `function` 은 아래와 같은 형태로 나타나야 합니다.

```cpp-formatted
#include <functional>
#include <iostream>
#include <string>

class A {
  int c;

 public:
  A(int c) : c(c) {}
  int some_func() {
    std::cout << "비상수 함수: " << ++c << std::endl;
    return c;
  }

  int some_const_function() const {
    std::cout << "상수 함수: " << c << std::endl;
    return c;
  }

  static void st() {}
};

int main() {
  A a(5);
  std::function<int(A&)> f1 = &A::some_func;
  std::function<int(const A&)> f2 = &A::some_const_function;

  f1(a);
  f2(a);
}
```

성공적으로 컴파일 하였다면

```exec
비상수 함수: 6
상수 함수: 6
```

와 같이 나옵니다.

```cpp-formatted
std::function<int(A&)> f1 = &A::some_func;
std::function<int(const A&)> f2 = &A::some_const_function;
```

위와 같이 원래 인자에 추가적으로 객체를 받는 인자를 전달해주면 됩니다. 이 때 상수 함수의 경우 당연히 상수 형태로 인자를 받아야 하고 (`const A&`), 반면에 상수 함수가 아닌 경우 단순히 `A&` 의 형태로 인자를 받으면 되겠습니다.

참고로 이전의 함수들과는 다르게 `&A::some_func` 와 같이 함수의 이름 만으로는 그 주소값을 전달할 수 없습니다. 이는 C++ 언어 규칙에 때문에 그런데, 멤버 함수가 아닌 모든 함수들의 경우 함수의 이름이 함수의 주소값으로 암시적 변환이 일어나지만, **멤버 함수들의 경우 암시적 변환이 발생하지 않으므로 `&` 연산자를 통해 명시적으로 주소값을 전달해줘야 합니다**.

따라서 아래와 같이 호출하고자 하는 객체를 인자로 전달해주면 마치 해당 객체의 멤버 함수를 호출한 것과 같은 효과를 낼 수 있습니다.

```cpp-formatted
f1(a);
f2(a);
```

위와 같이 말이지요.

### 멤버 함수들을 함수 객체로 - mem_fn

예를 들어서 `vector` 들을 가지는 `vector` 가 있을 때, 각각의 `vector` 들의 크기들을 벡터로 만들어주는 코드를 생각해봅시다.

```cpp-formatted
#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
using std::vector;

int main() {
  vector<int> a(1);
  vector<int> b(2);
  vector<int> c(3);
  vector<int> d(4);

  vector<vector<int>> container;
  container.push_back(b);
  container.push_back(d);
  container.push_back(a);
  container.push_back(c);

  vector<int> size_vec(4);
  std::transform(container.begin(), container.end(), size_vec.begin(),
            &vector<int>::size);
  for (auto itr = size_vec.begin(); itr != size_vec.end(); ++itr) {
    std::cout << "벡터 크기 :: " << *itr << std::endl;
  }
}
```

`transform` 함수는 `<algorithm>` 라이브러리에 있는 함수인데, 각 원소들에 대해 인자로 전달된 함수를 실행시킨 다음 그 결과를 전달된 컨테이너에 넣어줍니다. 함수 정의를 살짝 살펴보면 아래와 같습니다.

```cpp-formatted
template <class InputIt, class OutputIt, class UnaryOperation>
OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first,
                   UnaryOperation unary_op) {
  while (first1 != last1) {
    *d_first++ = unary_op(*first1);
    first1++;
  }
  return d_first;
}
```

여기서 문제는 해당 함수를 아래와 같이 호출한다는 점입니다.

```cpp-formatted
*d_first++ = unary_op(*first1);
```

`unary_op` 가 멤버 함수가 아닐 경우 위와 같이 호출해도 괜찮습니다. 하지만 문제는 `unary_op` 가 멤버함수 일 경우 입니다.

사실 위 코드를 컴파일 하면 아래와 같은 컴파일 오류가 나게 됩니다.

```compiler-warning
In file included from /usr/include/c++/7/algorithm:62:0,
                 from test2.cc:5:
/usr/include/c++/7/bits/stl_algo.h: In instantiation of '_OIter std::transform(_IIter, _IIter, _OIter, _UnaryOperation) [with _IIter = __gnu_cxx::__normal_iterator<std::vector<int>*, std::vector<std::vector<int> > >; _OIter = __gnu_cxx::__normal_iterator<int*, std::vector<int> >; _UnaryOperation = long unsigned int (std::vector<int>::*)() const noexcept]':
test2.cc:21:85:   required from here
/usr/include/c++/7/bits/stl_algo.h:4306:24: error: must use '.*' or '->*' to call pointer-to-member function in '__unary_op (...)', e.g. '(... ->* __unary_op) (...)'
  *__result = __unary_op(*__first);
              ~~~~~~~~~~^~~~~~~~~~
```

왜 그럴까요? 이 역시 전달된 `size` 함수가 멤버 함수여서 발생하는 문제 입니다. 위 템플릿에 `&vector<int>::size` 가 들어간다면 해당 `unary_op` 를 호출하는 부분은 아래와 같이 변환됩니다.

```cpp-formatted
unary_op(*first1);
```

가

```cpp-formatted
&vector<int>::size(*first);
```

꼴로 되는데, 멤버 함수의 경우

```cpp-formatted
(*first).(*&vector<int>::size)
```

혹은

```cpp-formatted
first->(*&vector<int>::size)
```

와 같이 호출해야 하기 때문 입니다. (이는 C++ 의 규칙 이라 생각하시면 됩니다. 위 컴파일러 오류 메세지를 읽어보세요!) 따라서 이를 위해서는 제대로 `std::function` 으로 변환해서 전달해줘야 합니다.

```cpp-formatted
#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
using std::vector;

int main() {
  vector<int> a(1);
  vector<int> b(2);
  vector<int> c(3);
  vector<int> d(4);

  vector<vector<int>> container;
  container.push_back(a);
  container.push_back(b);
  container.push_back(c);
  container.push_back(d);

  std::function<size_t(const vector<int>&)> sz_func = &vector<int>::size;

  vector<int> size_vec(4);
  std::transform(container.begin(), container.end(), size_vec.begin(), sz_func);
  for (auto itr = size_vec.begin(); itr != size_vec.end(); ++itr) {
    std::cout << "벡터 크기 :: " << *itr << std::endl;
  }
}
```

성공적으로 컴파일 했다면

```exec
벡터 크기 :: 1
벡터 크기 :: 2
벡터 크기 :: 3
벡터 크기 :: 4
```

와 같이 잘 나옴을 알 수 있습니다.

하지만 매번 위 처럼 `function` 객체를 따로 만들어서 전달하는 것은 매우 귀찮습니다. 따라서 C++ 개발자들은 라이브러리에 위 `function` 객체를 리턴해버리는 함수를 추가하였습니다.

```cpp-formatted
#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
using std::vector;

int main() {
  vector<int> a(1);
  vector<int> b(2);
  vector<int> c(3);
  vector<int> d(4);

  vector<vector<int>> container;
  container.push_back(a);
  container.push_back(b);
  container.push_back(c);
  container.push_back(d);

  vector<int> size_vec(4);
  transform(container.begin(), container.end(), size_vec.begin(),
            std::mem_fn(&vector<int>::size));
  for (auto itr = size_vec.begin(); itr != size_vec.end(); ++itr) {
    std::cout << "벡터 크기 :: " << *itr << std::endl;
  }
}
```

성공적으로 컴파일 하였다면

```exec
벡터 크기 :: 1
벡터 크기 :: 2
벡터 크기 :: 3
벡터 크기 :: 4
```

와 같이 잘 나옵니다. `mem_fn` 함수는 이름 그대로, 전달된 멤버 함수를 `function` 객체로 예쁘게 만들어서 리턴해줍니다.

```lec-warning
참고로 `mem_fn` 은 그리 자주 쓰이지는 않는데, 람다 함수로도 동일한 작업을 수행할 수 있기 때문입니다. 위 코드의 경우 `mem_fn(&vector<int>::size)` 대신에 `[](const auto& v){ return v.size()}` 를 전달해도 동일한 작업을 수행합니다. 

`mem_fn` 을 사용하기 위해서는 `<functional>` 헤더를 추가해야 하지만 람다함수는 그냥 쓸 수 있으니 좀 더 편리한 면이 있습니다. 물론, 코드 길이 면에서는 `mem_fn` 을 사용하는 것이 좀더 깔끔한 편입니다.
```

### std::bind

재미있게도 함수 객체 생성 시에 인자를 특정한 것으로 지정할 수 도 있습니다. 아래 예제를 보실까요.\sidenote{참고로 자바스크립트를 배우신 분들은 알겠지만 자바스크립트에도 똑같은 작업을 수행하는 bind 함수가 있습니다 :)}

```cpp
#include <functional>
#include <iostream>

void add(int x, int y) { std::cout << x << " + " << y << " = " << x + y << std::endl; }

void subtract(int x, int y) {
  std::cout << x << " - " << y << " = " << x - y << std::endl;
}
int main() {
  auto add_with_2 = std::bind(add, 2, std::placeholders::_1);
  add_with_2(3);

  // 두 번째 인자는 무시된다.
  add_with_2(3, 4);

  auto subtract_from_2 = std::bind(subtract, std::placeholders::_1, 2);
  auto negate = std::bind(subtract, std::placeholders::_2, std::placeholders::_1);

  subtract_from_2(3);  // 3 - 2 를 계산한다.
  negate(4, 2);        // 2 - 4 를 계산한다
}
```

성공적으로 컴파일 하였다면

```exec
2 + 3 = 5
2 + 3 = 5
3 - 2 = 1
2 - 4 = -2
```

와 같이 나옵니다.

`bind` 함수는 이름 그대로 원래 함수에 특정 인자를 붙여(bind) 줍니다. 예를 들어서

```cpp-formatted
std::bind(add, 2, std::placeholders::_1);
```

위 예시의 경우 `add` 라는 함수에 첫 번째 인자로 2 를 `bind` 시켜주고, 두 번째 인자로는 새롭게 만들어진 함수 객체의 첫 번째 인자를 전달해줍니다. 따라서;

```cpp-formatted
add_with_2(3);
```

를 하였을 때, 원래 `add` 함수의 첫 번째 인자로는 2 가 들어가게 되고, 두 번째 인자로는 `add_with_2` 의 첫 번째 인자인 3 이 들어가겠지요. 만약에

```cpp-formatted
add_with_2(3, 4);
```

처럼 인자를 여러개 전달하더라도 뒤에 것들은 무시 됩니다.

```cpp-formatted
auto negate = std::bind(subtract, std::placeholders::_2, std::placeholders::_1);
```

위 경우는 어떨까요? `negate` 함수는 첫 번째 인자와 두 번째 인자의 순서를 바꿔서 `subtract` 함수를 호출하게 됩니다. 즉 `negate(3, 5)` 를 호출할 경우 실제로는 `subtract(5, 3)` 이 호출되겠지요.

`placeholders` 의 `_1, _2` 들은 일일히 정의된 객체들 입니다. 그 개수는 라이브러리 마다 다른데, `libstdc++` 의 경우 (g++ 에서 사용하는 C++ 라이브러리 입니다.) `_1` 부터 `_29` 까지 정의되어 있습니다.\sidenote{인자를 30 개 보다 많이 받는 함수들의 경우 bind 를 제대로 사용할 수 없겠지만, 인자를 30 개 받는 함수를 만들었다는 사실은 무언가 코드를 잘못 짰다는 뜻이기도 합니다 :) }

한 가지 주의할 점은, 레퍼런스를 인자로 받는 함수들의 경우 입니다.

```cpp-formatted
#include <functional>
#include <iostream>

struct S {
  int data;
  S(int data) : data(data) { std::cout << "일반 생성자 호출!" << std::endl; }
  S(const S& s) {
    std::cout << "복사 생성자 호출!" << std::endl;
    data = s.data;
  }

  S(S&& s) {
    std::cout << "이동 생성자 호출!" << std::endl;
    data = s.data;
  }
};

void do_something(S& s1, const S& s2) { s1.data = s2.data + 3; }

int main() {
  S s1(1), s2(2);

  std::cout << "Before : " << s1.data << std::endl;

  // s1 이 그대로 전달된 것이 아니라 s1 의 복사본이 전달됨!
  auto do_something_with_s1 = std::bind(do_something, s1, std::placeholders::_1);
  do_something_with_s1(s2);

  std::cout << "After :: " << s1.data << std::endl;
}
```

성공적으로 컴파일 하였다면

```exec
일반 생성자 호출!
일반 생성자 호출!
Before : 1
복사 생성자 호출!
After :: 1
```

와 같이 나옵니다.

보시다시피 `do_something` 함수의 경우 첫 번째 인자의 `data` 를 두 번째 인자의 `data + 3` 으로 만들어주지만, 실제로 `do_something_with_s1` 함수를 실행하였을 때 첫 번째 인자로 `s1` 을 전달했음에도 불구하고 `s1` 의 `data` 가 바뀌지 않음을 알 수 있습니다.

그 이유는 위 생성자 호출 메세지에서 확인할 수 있듯이 `bind` 함수로 인자가 **복사** 되서 전달되기 때문입니다. 따라서 이를 해결 하기 위해서는 명시적으로 `s1` 의 레퍼런스를 전달해줘야 합니다.

```cpp
#include <functional>
#include <iostream>

struct S {
  int data;
  S(int data) : data(data) { std::cout << "일반 생성자 호출!" << std::endl; }
  S(const S& s) {
    std::cout << "복사 생성자 호출!" << std::endl;
    data = s.data;
  }

  S(S&& s) {
    std::cout << "이동 생성자 호출!" << std::endl;
    data = s.data;
  }
};

void do_something(S& s1, const S& s2) { s1.data = s2.data + 3; }

int main() {
  S s1(1), s2(2);

  std::cout << "Before : " << s1.data << std::endl;

  // s1 이 그대로 전달된 것이 아니라 s1 의 복사본이 전달됨!
  auto do_something_with_s1 = std::bind(do_something, std::ref(s1), std::placeholders::_1);
  do_something_with_s1(s2);

  std::cout << "After :: " << s1.data << std::endl;
}
```

성공적으로 컴파일 하였다면

```exec
일반 생성자 호출!
일반 생성자 호출!
Before : 1
After :: 5
```

와 같이 실제로 `s1` 의 값이 잘 바뀌었음을 알 수 있습니다. `ref` 함수는 전달받은 인자를 **복사 가능한 레퍼런스** 로 변환해줍니다. 따라서 `bind` 함수 안으로 `s1` 의 레퍼런스가 잘 전달 될 수 있게 됩니다.

참고로 `const` 레퍼런스의 경우 `cref` 함수를 호출하면 됩니다.

그럼 이것으로 이번 강좌를 마치도록 하겠습니다. `function, mem_fn, bind` 들을 적재 적소에 잘 쓴다면, C++ 의 강력한 라이브러리를 좀 더 풍요롭게 사용할 수 있을 것입니다.

##@ chewing-cpp-end