Callable개념

모두의 코드 https://modoocode.com/254

C++ 에서는 () 를 붙여서 호출할 수 있는 모든 것을 Callable 이라고 정의

#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);
 }

의 성공적인 결과는 a + b = 8

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

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

#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();
}

function 객체는 템플릿 인자로 전달 받을 함수의 타입을 갖게 됩니다 <> 안에 들어가는것이 템플릿인자!!!!!

</span>

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

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

#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;
}

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

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 에 관한 정보도 추가로 전달해야 합니다.

#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);
}

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

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

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

Written on August 13, 2022