boost::*를 배워봅시다에 관해.

C++를 밥벌이로 삼은지 1년하고도 9개월 정도 되었습니다. (병특이 1년 하고도 3개월 남았다는 이야기네요. 하아)
그간, 이런 저런 삽질을 하다가 STL을 만났고 boost를 만났습니다만, STL의 강력하지만 모자란 부분을 훌륭히 매꿔주는
boost의 강력함에 매료되어 이곳 저곳에서 사용하기 시작했고, 이 강력함을 좀 널리 알려보고자 시리즈를 쓰고자 마음을
먹었습니다.
감히 한번 써보자고 마음을 먹었습니다.

그 1탄은 boost라이브러리중 가장 큰 임팩트를 남기며 감동을 주었던 boost::bind입니다.
smart_ptr, format, variant, lambda, pool, spirit등의 라이브러리를 다뤄볼 생각이구요.
여력이 된다면, python도 다룰 생각입니다. (boost에 포함되지는 않았지만 luabind도 다룰 생각입니다)

그 외에도 제가 만들어서 사용하는 유틸리티들도 다룰 계획인데, 이건 간간히 다루게 될 것 같군요.

STL/Boost의 강력함. 느껴보세요. 🙂

boost::bind를 배워봅시다.

1. 멋진 예제.

C++에서 코딩을 하다보면, 다음과 같은 코드를 실행할 일이 종종 생기곤 한다.

//무식한 코드
class A
{
public:
    void Start();
    int SomeWork(int,int,int);
};

std::vector<a> v;
...(some inits)...

for(std::vector<a>::iterator itr = v.begin();
     itr != v.end(); ++itr)
{
    // itr은 포인터가 아니므로 이게 더 맞는 표현이라고 한다..
    (*itr).Start();
}

vector에 담긴 class A의 instance에 대해서, Start 메소드를 일괄적으로 호출하는 경우인데, 이런 처리를 boost라이브러리의 bind(이하 boost::bind)를 사용하면 효과적으로 처리할 수 있다. (타이핑 수가 준다)

//이쁜코드.
#include <boost/bind.hpp>
class A
{
public:
    void Start();
};

std::vector<a> v;
... (add to v) ...
std::for-each(
    v.begin(),
    v.end(),
    boost::bind(&A::Start, _1)
);

언뜻 보아도, 2개의 bracket(중괄호)와 std::vector<A>::iterator라는 긴 타입선언이 사라졌다. 키햐.

2. 인수 고정과 자리표.

boost::bind는 기본적으로 여러개의 인수를 갖고 있는 호출 가능한 C++ 객체(함수, 함수자-operator()를 구현한 struct혹은 class)의 인수들을 고정시키는 역할을 한다. 예를 들어, 인수로 a,b,c를 가진 함수를 반복적으로 호출할때, 3개의 인수중 2개를 고정시키고 하나만 바꿔가며 호출해야하는 상황에 유용한 라이브러리이다.

//간단한 예제
int x_minus_y(int x, int y)
{
    return x-y;
}
// same as x_minus_y(x,3);
x_minus_3 = boost::bind(x_minus_y, _1, 3);

위의 예제는 주어진 정수 x,y를 빼서 그 결과를 반환하는 x_minus_y라는 함수의 2번째 인자를 3으로 고정시킨 호출가능 객체(함수자)인 x_minus_3을 리턴하고 있다. 2번째 인자가 3으로 고정되었기 때문에, x_minus_3을 호출할 때는 다음과 같이 정수 하나만 넘겨주면 된다.

//호출 예제
x_minus_3(2); // 결국은 minus(2,3)과 동일한 효과를 갖는다.

x_minus_3을 생성하기 위해 사용한 구문에서 보면, _1이라는 특이한 표현을 사용하고 있는데, 이것이 자리표(placeholder)이다. 자리표는 bind에 의해 생성된 함수자가 받을 인수를 의미한다. _1은 첫번째로 받을 인수, _2는 두번째, _n은 n번째의 인수가 되겠다.

이를 응용하면 x_minus_y를 이용해, y에서 x를 뺀 결과를 넘겨주는 y_minus_x를 만들 수도 있다.

//y_minus_x
y_minus_x = boost::bind(x_minus_y, _2, _1);

위의 구문이 생성한, y_minus_x는 2번째로 받은 인수(_2)를 첫번째에 넣고, 첫번째로 받은 인수(_1)을 두번째에 넣어 x_minus_y를 호출해 준다. 이 자리표를 이용하면, 상당히 많은 응용을 할 수 있게 된다.

물론, 자리표를 사용하지 않고, 인수를 받지 않는 함수자를 생성할 수도 있다.

3_minus_2 = boost::bind(3_minus_2, 3, 2);

3_minus_2의 호출 결과는 언제나 1이 될 것이다.

3. 멤버함수와 bind

멤버함수를 bind와 사용하기 위해서는 boost::mem_fn이란 라이브러리에 대한 이해가 필요하다. 간단히 예제를 통해 알아보면,

//1절에서 사용한 class A를 그대로 쓰겠습니다아-
a_somework_functor = boost::mem_fn(&A::SomeWork);
A a_instance;
// same as a_instance.SomeWork(1,2,3);
a_somework_functor(a_instance, 1, 2, 3);

위와 같은 구문에 의해 생성된 a_start_functor함수자는 첫번째 인수로 주어지는 A의 인스턴스를 이용해 SomeWork을 호출하게 된다. C++에서 인스턴스와 멤버함수의 포인터를 이용해 함수를 호출하는 정식 구문이 없고, 이를 구현하기 위해서는 operator .*나 operator .->를 활용해야 하는데, 이 작업이 생각보다 복잡하므로, 이러한 함수자의 형태로 편리하게 이용하는 것이다. (자세한 사항은 Modern C++ Design 5장 참고)

1장에서 사용했던 예제를 다시 가져와서 살펴보자.

std::for-each(v.begin(), v.end(), boost::bind(&A::Start, _1));

boost::bind가 사용자의 편의성을 고려해주지 않았다면 boost::bind(&A::Start, _1) 대신 boost::bind(boost::mem_fn(&A::Start), _1)을 사용했어야 할 것이다. 하지만, 영리한 boost::bind는 첫번째 인수로 멤버함수가 올 경우 알아서 boost::mem_fn을 이용해 처리를 해준다.

boost::bind(&A::Start, _1)에 의해 생성된 함수자는 첫번째 인수로 A의 인스턴스를 넣어주어야 한다. 즉, boost::bind(&A::Start, _1)(a)는 a.Start()와 같은 의미를 갖게 되는 것이다.

std::for-each 구문은 첫번째와 2번째 인수로 주어진 iterator의 범위 내에서 각각의 요소에 대한 참조를 3번째 인수로 주어진 함수자를 이용해 호출한다. (STL 관련 서적 참고) 따라서 v에 들어있는 모든 a의 인스턴스에 대해 멤버함수인 A::Start()를 호출하게 된다.

4. 합성 함수.
수학 시간을 되돌려 보자. f(g(x))라는 수식이 기억 나는가? 그렇다. g(x)의 결과를 이용해 f(x)에 대입시켜 그 결과를 사용하는 합성함수이다. boost를 사용하면 C++에서 합성함수를 쉽게 만들 수 있다.

//합성함수예제
int f(int x)
{
    return x+3;
}

int g(int y)
{
    return (-1)*y;
}

g_f_x = boost::bind(g, boost::bind(f, _1)); // same as g(f(x))
f_g_x = boost::bind(f, boost::bind(g _1)); // same as f(g(x))

g_f_x 함수자를 만들때 사용된 bind의 예를 보면, g의 첫번째 인수를 boost::bind(f, _1)로 주고 있다. 이는 boost::bind(f, _1)의 결과를 첫번째 인수로 주겠다는 의미가 되며, 이는 우리가 원하던 합성함수이다!

합성함수를 잘 이용하면 세상이 편해진다.

struct Item{
   int score();
   Summary get_summary();
};

struct Summary
{
    bool check();
};

std::vector<item>; v;

v에 들어있는 Item중 get_summary().check()의 결과가 true가 되는 Item의 인스턴스를 찾는 문제를 생각해보자. 이런 경우에 bind가 또 한번 괴력을 발휘한다.

//합성함수를 이용한 찾기 예제.
std::find_if(
    v.begin(),
    v.end(),
    boost::bind(
        &Summary::check,
        boost::bind(Item::get_summary, _1)
    )
);

여러 줄로 나눠서 썼지만, 실은 한줄이다! get_summary(_1).check()를 bind를 이용해 표현하면 이렇게 되는 것이다.

5. 정렬.

boost 1.33에 업데이트된 bind는 <, >, ==와 같은 기초 비교 연산자를 지원한다. 이게 무슨 소리인가 싶지만, 예제를 보면 감이 올 것이다.

std::sort(
    v.begin(),
    v.end(),
    boost::bind(&Item::value, _1) < boost::bind(&Item::value, _2)
);
&#91;/cpp&#93;
v에 들어있는 Item들을 Item::value의 결과값을 이용해 정렬하는 예제이다. 만세! bind에 의해 생성된 함수자에 operator<를 오버로딩 하여, <가 있을 경우 비교하는 함수자를 자동으로
생성해 주는 기능! 현재, ==, !=, <=, >=, <, > 를 지원한다고 하니 매우 편리할 듯 싶다.

좀 더 알아보면,

[cpp]
std::find_if(
    v.begin(),
    v.end(),
    boost::bind(&Item::value, _1) == 3);

위와 같이 Item::value()의 결과값이 3인 요소를 찾는 것도 쉽게 가능하다.

6. 결론

클래스와 클래스들의 컨테이너, 그리고 STL의 강력한 알고리즘을 사용하는데 상당한 애로사항이 존재했던 것이 사실이다. 매번 함수자를 만들어 작업해야 했던 옛날과 비교하면 행복할 따름이다. boost::bind는 STL의 진정한 강력함을 느끼기 위해 필수적으로 사용해야 하는 라이브러리라고 생각된다. (다행히 STL확장과 관련된 Draft에 보면 이런 형태의 bind가 기본으로 들어올 듯 싶다)

또한, bind와 함수자를 적절히 잘 이용한다면, 콜백과 같은 기능을 구현하는데 매우 유용할 수 있다. 또, Command패턴을 이용하는데도 매우 유연하게 적용될 수 있다.

A. boost::function
예제에서 사용한 boost::bind의 결과물 들은 타입을 지정하지 않은 채 변수에 대입되고 있다. 귀찮아서 생략한 것이고, 실제로는 타입을 지정해줘야 하는데, 그 타입은 boost::function을 사용하면 쉽게 할 수 있다. boost::function은 함수자를 정의하는 라이브러리라고 생각해도 무방하며, 선언된 변수에는 signature가 맞는 어떤 호출가능한 객체라도 넣을 수 있으니 참고하시길.

쓰는 법은 다음과 같다.

// 리턴값이 없고, 인수가 int 하나인 함수 f1
boost::function<void (int)> f1;
boost::function<int (double, std::string&)> f2;

// int를 리턴하고, double과 std::string의 참조를 받는 함수 f2

다만, VC6와 같은 오래된 컴파일러에서는 구문이 다소 달라질 수 있으므로 참고하시길.

B. 참조
boost에서 함수자와 관련된 라이브러리를 사용하다보면, 종종 위험한 상황이 종종 발생한다.

std::string a = "AAAAAA";
// a의 복사본이 전달된다!
boost::bind(some_function, a);
// a의 레퍼런스가 전달된다!
boost::bind(some_function, boost::ref(a));

C++의 특성상 발생하는 문제라고 보여지는데, bind 템플릿을 전개할때, 참조로 호출해야하는 상황에 복사본이 전달되는 상황이 발생할 수 있다. 이를 해결하기 위해, boost::ref가 존재한다. 그냥 쉽게 boost::ref를 안쓰면 전부 call-by-value이고, 쓰면 call-by-reference라고 생각하시면 되겠다.

C. References

boost의 함수관련 문서들
Modern C++ Design, Andrei Alexandrecu, Addison Wesly (이기형 역, 인포북 발간)

ps. 피드백 부탁 드립니다.
ps2. 불펌 금지입니다.
ps3. 워드프레스로 옮겼더니 깨지더군요. 그래서 수정.
ps4. 멤버함수 관련된 부분이 수정되었습니다. A::Start라고 쓰면, 표준에 어긋나더군요. &를 붙여서 함수주소를 넘기는 방법이 정확한 방식입니다.

프로그래머의 3대 덕목.

회사 인트라넷에 대충 올렸던 걸 다시 정리해서..말하자면 재활용입죠. 하하.

세계에서 가장 많이 쓰이는 스크립트 언어중 하나인 perl을 만든 Larry Wall아저씨는, laziness가 프로그래머의 덕목이라고 주장을 합니다아. 맞는 말이죠. Randal Schwartz라는 사람은 Camel Book에서 3대 덕목으로 확장시켜서 주장을 합니다..

원문 : Virtues of a Perl Programmer

펄 프로그래머의 덕목이라고 되어있지만, 실은 모든 프로그래머에게 적용되는 사항인듯 합니다.

머리아프다고.. 영어라고.. 저 쪽에서부터 돌이 날아오는 소리가 들리기에 대충 요약 해보지요.


1. Laziness – 게으름 혹은 귀차니즘.

에너지 소모를 줄이기 위한 노력을 하게끔 하는 녀석입니다. 요는 프로그래밍을 할 때 최대한 노동량을 줄이기 위한 방향으로 짠다는 것이지요. 예를 들면 자주쓰는 루틴 같은 것은 라이브러리로 빼놓는 다 던지, 상속관계를 잘 사용해서 타이핑 수를 줄이거나 생각해야 하는 량을 줄인다던지 하는 것들입니다. 이런 식의 작업을 하다보면, 모듈화/라이브러리화가 진행되기 마련이지요..

모듈화/라이브러리화가 진행되면 다른 사람들도 사용하게 됩니다. – 열린소스이든 닫힌소스이든 마찬가지지요. 혼자 코딩하는 것이 아니라면 말이죠.. – 이렇게 될 경우, 작성한 프로그래머는 수없이 많은 질문의 바다에 빠져 허우적대기 시작합니다. 그럼 그 질문들이 귀찮아서 문서화를 하게 되지요. 한번의 투자로 반복을 하지 않는 것. 이것이 게으름 혹은 귀차니즘 입니다. 멋지지 않나요?

2. Impatience – 참을성 없음

컴퓨터가 못놀게 하는 겁니다. 그때 그때 필요한 코드만 작성하는 것이 아니라, 앞으로 필요해질 것들까지 작성하는 것이지요. 꼭 작성하지 않더라도 최소한 준비는 하는 겁니다. 확장성이라든지 기타 등등.

할 수 있을때 미리미리 하거나, 아니면 미리미리 준비하는 참을성 없음. 참 아름다운 덕목입니다. 어찌보면, laziness의 확장판이라고도 할 수 있지요.

3. Hurbis – 자만

하늘을 찔러 구멍을 내고 바다를 찍어 마르게 할 정도의 자존심! 감히 단점이 언급되지 않을 정도의 프로그램을 작성하게 하는 자기만족! 더 말할 나위 없이 세번째!


참 글을 재밌게 쓰는 사람들 입니다. 하하.
전적으로 동의하게 만드는 사항들이지요. 하아. 언제쯤이면 3번째를 만족시키며 살 수 있을까나..