본문 바로가기

Programming

Closure in C++

Closure in C++

Closure 는 Lexical binding 을 지원하는 함수형 언어에서 주로 쓰이던 개념으로서, 코드 블럭이 자신이 정의될 때의 주변 환경(코드 블럭이 정의될 때의 로컬 변수들의 값)을 기억하여 나중에 수행될 때도 원래의 환경에 따라 수행되는 것을 말한다. 보통 closure를 람다 함수(익명 함수)와 혼동하는 경우가 많은데 둘은 완전히 다른 개념이며 구분해서 사용해야 한다. 이러한 혼동이 생기는 이유는 closure 가 익명함수와 묶여서 사용되는 경우가 많기 때문이다. 또 closure 를 함수의 인수 또는 결과값으로 사용될 수 있는 코드 블록이라 이해하는 것은 closure 자체에 대한 이해라기 보다는 closure 용도에 대한 이해에 해당한다.

C++ 은 대표적인 절차적 언어이고 포인터를 지원하는 low level language 로서 성능 면에서는 가장 파워풀한 언어이다. 하지만 C++ 은 또한 가장 유연한 언어로서 라이브러리를 통해 확장될 수 있다. Template 와 Boost 라이브러리를 이용하여 C++ 에서 함수형 언어의 장점을 아주 쉽게 사용할 수 있으며, 함수형 언어의 대표적 개념들인 anonymous function, closure, higher order function 등이 지원된다.


1. Closure 의 개념

일단 closure 를 사용하기 위해서는 언어가 lexical binding 을 지원해야 한다.

다음의 C++ 코드를 보면,

        int a = 7, b = 3;
   
        function<void (int)> f = ( cout << a * _1 + b );  // 함수 f 는 일차 함수 f(x) = a*x + b 에 해당하는 람다 함수

        f(5);  // 7 * 5  + 3 = 38 를 출력

        a = 10, b = 20;

        f(5);  // 7 * 5  + 3 = 38 ? or 10 * 5  + 20 = 70 ?

코드의 두번 째 줄에서 정수를 입력받아 여기에 이미 정의된 a 와 b를 계수로 하는 일차 함수 f 를 정의하였다(using boost). 따라서 세번 째 줄의 f(5) 는 당연히 38의 값을 출력할 것이다.
그 후 a 값과 b 값이 변경되었는데, 다시 f(5) 를 호출하면 어떤 값이 출력될까? a 의 값이 바뀌었음에도 함수 f 는 자기가 정의되던 당시의 환경을 기억하고 있으며 따라서 당시의 a 값인 7 에 따라 역시 결과값으로 38 을 출력하게 된다. 다른 말로 하면 함수 f 는 a 와 b 의 값에 "Closed over(Closure)" 되었다고 할 수 있다. 물론 위와 같은 코드가 의미를 갖는 경우는 a 와 b 의 값들이 런타임에 결정되고 이에 따라 closure 가 정의되는 경우이다.
이와 갈은 변수의 바인딩 방식을 static(lexical) binding 이라 부르며 변경된 변수 값이 동적으로 반영되는 dynamic binding 과 구분된다. C++ 에서 fully quallified 되지 않은 변수는 정적 바인딩된다.
이 예에서 함수 f 에서 사용되는 변수 a, b 는 코드 블록 바깥에서 정의되며 이러한 변수를 자유 변수(free variable, C# 의 outer variable)라고 한다.
Closure 는 이러한 free variable(블럭 안에서 바인딩되지 않는 변수)을 포함하는 코드 블록으로서, Closure 는 다른 함수의 인수로서 사용되거나 리턴 값으로 출력될 수 있다. Closure 내의 free variable 은 코드 블록이 정의되는 당시의 환경에 의해 결정된다.

Closure 는 LISP 나 루비와 같은 언어에서는 자연스럽게 쓰이고 있다.

다음은 LISP 의 한 dialect 인 Clojure 에서의 예.

         (defn addList[lst n] (map (fn[x](+ x n)) lst))

         ( addList '(1, 3, 5), 100 ) => (101, 103, 105)

여기에서 밑줄 부분의 익명함수는 다른 함수 map 의 인수로 사용되며 외부에서 정의되는 변수 n을 포함하고 있다.

C# 에서도 anonymous delegate 를 이용하여 Closure 를 사용할 수 있게 되었다.
다음은 함수 y = ax + b 의 상수 a, b 이 정해진 후 변수 x 를 대입하여 결과값을 계산하는 코드이다.

        delegate int calculate(int a);

        static void Main(string[] args)
        { 
            int a = 3, b = 5;

            y = Calc( delegate(int x) { return a * x + b; } , 5);           
        }
       
        static int Calc(calculate cal, int x)
        {return cal(x);}


위 코드에서 밑줄 친 부분은 Closure 의 역할을 하는 익명함수이며 일차함수 y = a*x + b 를 정의하면서  outer variable 인 a, b 를 참조하고 있다. 이 익명함수는 다른 함수 Calc 의 인수로 사용되어 함수값을 출력한다.
C# 코드는 같은 역할을 하는 C++ 코드보다 복잡해 보이는데, C# 은 지나치게 언어의 간결성을 강조한 나머지 프로그래머의 자유를 제한하는 느낌이 있다. 이에 반해 C++ 에서는 프로그래머가 마음만 먹으면 무엇이든 할 수 있다. 성능 좋은 라이브러리를 사용함으로써 함수형 프로그래밍은 오히려 C++ 에서 더 잘 구현될 수 있다.


2. 왜 Closure 를 사용하는가?

자바의 차기 버전에서 Closure 의 지원이 무산되면서 자바 커뮤니티에는 많은 실망의 글들이 올라왔다. 실제로 새로운 자바에서 구현되기를 바라는 기능의 1위가 Closure 였다고 한다. 특히 루비와 같이 Closure 를 일상적으로 사용하는 언어의 사용자들이 큰 실망감을 나타내었다.
Closure 가 유용한 이유는 맨 처음 C++ 코드 예에서 볼 수 있듯이 late evaluation 이 가능하다는 것인데, 일단 특정 환경에서 함수를 정의한 후 나중에 함수를 호출하여 실행시킬 수 있다. 이 때 함수는 바뀐 주변 환경과 상관없이 자신이 정의될 때의 환경에 따라 행동한다.

예를 들어 일차 함수의 계수들을 런타임에 계산하는 경우에,

    void funcAB(int &a , int &b)
    { a = 7; b = 3; }

    int main()
    {
        int a, b;
        funcAB(a, b);
        cout << a * 5 + b << endl;

        return 0;
    }

위와 같이 계수를 계산하는 함수를 이용하여 레퍼런스로 값을 저장할 수도 있지만, 아래 예처럼 일차 함수 자체를 리턴하도록 하면 더욱 직관적일 것이다.

    function<void (int)> funcAB()
    {
        int a = 7, b = 3;
        function<void (int)> f = ( cout << a * _1 + b << endl);
        return f;
    }

    int main()
    {
        function<void (int)> f = funcAB();      
        f(5);

        return 0;
    }

이 예에서 함수 funcAB() 는 일차 함수를 출력하는 함수를 리턴 값으로 하며 이 리턴된 함수는 funcAB() 의 scope 바깥에서도 a 와 b 값을 보존한다.

이러한 closure 의 특징은 하나의 환경 하에서 여러 개의 코드 블록을 정의하여 이후에 실행시킬 수 있는 이른바 multiple function 의 사용을 가능하게 한다.

이와 같이 C++ 언어는 함수형 언어의 장점을 적극적으로 수용하였으나 정작 C++ 프로그래머들은 이의 활용에 매우 소극적인 것 같다.  이들 기능 중 일부는 새로이 제정되는 C++ standard 에도 들어갈 예정이며, 실제로 이러한 기능들을 사용함으로써 C++ 프로그래밍의 패러다임 자체를 바꿀 수 있다고 한다.

'Programming' 카테고리의 다른 글

Maemo 5 개발환경  (0) 2010.01.09
QT 기반으로 새로 태어나는 Maemo 6  (0) 2010.01.03
안드로이드 플랫폼에서 Clojure 실행  (0) 2009.06.14
Clojure Data Structure  (0) 2009.05.22
Clojure - A modern LISP dialect  (0) 2009.05.22