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 그리고 내가 예전에 생각한 것처럼, 미래(?)가 되어 일이 좀 편해지는 듯 하다.