rein's world

C++0x를 써서 Closure 다시 만들기

예전에 포스팅 한 것 처럼, 2 년 전, 이맘 때 쯤, boost 라이브러리를 이용해서 C++ 용 Closure 구현체를 만들었다. 이걸 만들 때 겪었던 어려운 점들은 다음과 같다.

  • 함수의 타입을 인식해야 하기 때문에 가변 인자 템플릿을 만들어야 했고, 이를 위해 boost::preprocessor 의존성이 생겼다
  • 인자의 생명 주기를 추정할 수 없기에, 간단한 가정을 하고 모든 참조형 인자(T& 타입)를 강제로 원래 타입으로 지정(T 타입)해서 저장하도록 했다.
  • 함수를 ‘저장’하기 위해 boost::function 을 사용했다. (std::tr1::function 과 동일함)

C++ 0x 에는 lambda function 문법이 있다. 이를 이용해서 위의 세 가지를 간단히(?)해결할 수 있게 되었다.

원래 다음 템플릿에 해당하는 걸 썼다.1

/// 인자가 4개인 멤버함수의 Closure
template<typename T, typename R, typename A1, typename A2,
  typename A3, typename A4 >
class Closure4 : public Closure {
 protected:
  typedef function<R (T&, A1, A2, A3, A4)> FT;
  T& m_Obj;
  FT m_Function;
  A1 m_A1;
  A2 m_A2;
  A3 m_A3;
  A4 m_A4;
 public:
  Closure4(T& obj, FT func, A1 a1, A2 a2, A3 a3, A4 a4)
    : m_Obj(obj), m_Function(func), m_A1(a1), m_A2(a2),
      m_A3(a3), m_A4(a4) { }
  virtual ~Closure4() { }
  virtual void Execute() override {
    m_Function(m_Obj, m_A1, m_A2, m_A3, m_A4);
  }
};

익명 함수 문법인 lambda function을 사용하면 다음 한 가지 형태로 내가 이전 구현에 생각했던 모든 함수 형을 다룰 수 있다.

template<typename fun>
class LambdaClosure : public Closure {
  fun toExec;
 public:
  LambdaClosure(fun& f) : toExec(f) {}
  virtual ~LambdaClosure() {}
  virtual void Execute() override sealed { toExec(); }
};

우선 함수의 타입 인식 문제. 타입 이름을 알 수 없지만(익명 함수니까), 이건 템플릿 함수의 타입 추론이 해결해준다.  아래 함수를 만들어서 해결.

```c++
template<typename fun>
std::tr1::shared_ptr<Closure> MakeLambdaClosure(fun f) {
 return std::tr1::shared_ptr<Closure>(new LambdaClosure<fun>(f));
}

함수 자체의 타입이야 있겠지만, 이걸 굳이 boost::function 혹은 std::(tr1::)function 에 넣지 않고도 저장할 수 있다.

그리고 인자의 생명 주기 자체는 내가 작성하는 Closure 코드에서 전부 알아서 할 게 아니라, lambda function의 argument capture 문법에서 원하는 데로 참조인지 복사인지 각 인자 별로, 혹은 적당히 지정하면 끝.

마지막으로 추가 라이브러리 사용이 없다는 건 장점. 게다가 lambda function은 선언하는 scope 에서 friend 함수로 간주되기에, 이전에는 어쩔 수 없이 public 으로 노출해야 하던 함수들이 굳이 노출될 필요가 없어진다. 그리고 기나긴 template instantiation 된 함수 호출들이 호출 한 번으로 끝나기 때문에 함수 호출 오버헤드도 약간 감소한다.2

덤으로, 예전대로라면 별도의 함수를 작성해야 하는데, 그 부분이 없어지고, 실제로 의미를 따라가기 쉽게(혹은 어렵게?) “Closure를 생성하는 위치”에 함수 본문(body)이 온다는 것도 꽤 편하다.

일단 MS VisualStudio 2010 과 GCC 4.5 에는 이 익명함수가 존재하고 있고, draft 단계를 지나고 나면 아마 더 많은 컴파일러 들도 지원할 것으로 보인다.3 그리고 내가 예전에 생각한 것처럼, 미래(?)가 되어 일이 좀 편해지는 듯 하다.


  1. 실제로는 BOOST_PP가 이걸 생성한다. ↩︎

  2. 아주 간단하게 객체 내부 변수를 1씩 더하는걸 이전 구현과 새 구현 기준으로 비교했더니 Core2Duo 6750 에서 597ms vs. 181ms 정도의 차이가 있었다; 실행 횟수는 8천만번 호출하는걸 256회 반복해서 낸 평균; 그러니까 어느 쪽도 충분히 큰 오버헤드는 아니다. ↩︎

  3. intel cc도 지원하는데 정확한 시작 버전을 모르겠다. ↩︎