boost::array를 배워봅시다.

1. Old. Old. Old style

char buffer[1024];
...(기타 여러 코드들)...
sock.recv(buffer, 1024);

혹시 위와 같은 스타일의 코드를 작성하고 있지는 않으신가요? 물론, 잘 작동하고 문제가 없는 코드일겁니다. 하지만, 여기엔 문제가 하나 있습니다. buffer는 배열이지 객체가 아닙니다. iterator도 없으며, 멤버함수도 없습니다. 불편할 뿐더러 위험합니다. 한번 볼까요?

char buffer[1024];
...(기타 여러 코드들. 복잡해서 눈에 잘 안들어옴)...
sock.recv(buffer, 4096); // 뭔가의 필요에 의해 고쳐졌군요.

와우. 망했습니다. 스택이 깨지면서 어떤 문제가 발생할지 감도 잡기 힘듭니다. 주로 보안사고가 이런 코드에서 많이 발생합니다. buffer overflow죠. 이런 류의 “실수”를 하지 않는다고 보장할 수 있을까요? 네. 이런걸 막기 위해 사람들은 이렇게 작업하기도 합니다.

#define BUFFER_SIZE 1024
char buffer[BUFFER_SIZE];
...(기타 여러 코드들. 복잡해서 진짜 눈에 잘 안들어옴)...
sock.recv(buffer, BUFFER_SIZE);

별 문제 없어보입니다. 하지만, 버퍼의 크기가 각 위치마다 달라져야 한다면.. 그 수만큼 #define도 늘어날겁니다. enum을 쓰더라도 이건 매한가지죠.

그렇다면, 이런건 어떨까요.

std::vector<char> buffer;
buffer.resize(1024);
sock.recv(&buffer[0], buffer.size());

훌륭하죠. 이제 C++답습니다. 하지만, buffer는 여전히 stack변수이며 이 부분이 실행될때마다 매번 새로 만들어질겁니다. 그리고, 매번 새로 만들어진다는건… 매번 할당자를 호출해야한다는 겁니다. (쉽게말해 new/malloc이 매번 호출됩니다. 재앙이죠.)

이걸 막기 위해선, 그렇죠. buffer를 재사용하면 됩니다. 하지만, 재사용시에는 언제나 멀티쓰레드 환경을 고민해야하는 상황이 다가옵니다. 후.

좀 더 C++다우면서, 배열과 같은 효과를 내는건 불가능할까요?


2. olleh! array!

boost::array<char, 1024> buffer;
sock.recv(&buffer[0], buffer.size());

네. 완벽하게 C++ container스러우면서, 배열의 역할을 수행하는 자료구조입니다. boost::array이죠! 이녀석의 내부구조는 다음과 같습니다.

template<class T, int N>
class array
{
    ...(생략)...
    T elems[N];
};

유일하게 갖고 있는 멤버변수는 elems. 배열입니다! 결국 배열을 선언한 것과 동일한 효과를 갖습니다. 여기까지 왔으면, 파직 하고 뭔가 옵니다.

array는 iterator, const_iterator, size(), begin(), end(), front(), back(), empty()등의 STL컨테이너라면, 다들 갖고 있는 형식과 함수들을 갖추고 있습니다. 그러면서도 []연산자를 지원하여 기존의 배열과 흡사하게 사용할 수 있지요. 이것이 시사하는 바는 생각보다 큽니다.

기존의 배열에 알고리즘을 적용해봅시다.

char buffer[1024];
...(생략)...
char* found = std::find(buffer, buffer+1024, 'c');

위와 같은 알고리즘을 적용할 경우, 1024라는 크기상수가 지속적으로 따라다닙니다. 또한, buffer를 vector나 다른 자료구조로 바꾸기라도 하면, 꽤 많은 수정을 가해야하지요. 하지만, boost::array를 사용한다면 다음과 같이 바뀝니다.

// 편의를 위한 typedef. C++ 프로그래머의 친구죠. :)
typedef boost::array<char, 1024> buffer_type;
buffer_type buffer;
buffer_type::iterator found =
    std::find(buffer.begin(), buffer.end(), 'c');

3. 정리

사실, boost::array(TR1에 들어갔으니 이젠 tr1::array려나요.)는 굉장히 간단하고, 의미없게 느껴질 수 도 있습니다. 하지만, 과거의 배열은 원소들을 담는 컨테이너의 역할을 함에도 불구하고 STL스럽지 않다는 단점이 있었습니다. 자료구조를 다루는데 있어서, STL 컨테이너들과 굉장히 동떨어진 사용법은 코드의 일관성을 해치게 됩니다.

이런 면에서 볼 때, 배열과 동일한 구조/성능을 가지고 있으면서 STL 컨테이너와 유사한 인터페이스를 갖춘 array는 C++내에서 C의 유산에 의해 코드가 망가지는 것을 피할 수 있도록 해주는 상당히 중요한 도구입니다. 🙂 그리고, 200줄이 안되는 이 코드는 좋은 참고자료이기도 합니다. 시간 나시면 꼭 한번 읽어보시길. 🙂


A. References
boost::array의 문서

downcast overloading.

회사의 코드들이 상당히 계층적으로 묶여있는 상속관계… 즉, 결과와 관련된 모든 클래스는 ResultInterface라는 순수 가상 클래스(즉, 인터페이스)를 상속받고 있는 관계로 dynamic_cast를 if문으로 쭈욱 연결해서 해당 함수를 호출하는 코드가 일반적이었다.
더 보기 “downcast overloading.”

C++ Standard TR1 변경사항.

지난 3월에 C++ Standard Committee회의가 독일 베를린에서 있었다는군요. 이거 엄청난 뒷북이겠으나, 괄목할만한 것들이 들어있기에 언급하고 넘어가려고 합니다. (대체 이게 얼마만의 Development Note냐.. 반성좀 하자.)

일단, 소스는 Dr. Dobb’s Journal의 C++ Department (formerly C/C++ User’s Journal) 6월호(?)에 실린 Pete Becker씨의 Living by the Rules: Part II입니다. Pete Becker씨의 글은 참 읽기 편하고 즐거워요. 🙂 C++ Standard Committee 멤버이고, C++ Standard의 Project Editor로 계신답니다. 지금 C++ Library Standard TR1에 대한 책을 쓰고 계시다는군요. 기대중. 더 보기 “C++ Standard TR1 변경사항.”

STLPort팀에 패치를 보내다.

일하는 중간에 컴파일러를 Microsoft Visual C++ Compiler에서 Intel C++ Compiler로 바꿔보면 어떨까라는 생각이 문득 들기에..

삽질을 해버렸습니다. 므흣.

먼저 stlport 4.6.2로 삽질을 했는데, 하다보니 어차피 5.0으로 옮길거 귀찮기도 하고 해서 stlport 5.0.1로 버젼을 바꾸고 빌드 시도.

boost에서 std::type_info를 못찾는다고 징징거리고, new.h를 인클루드 할 수 없다고 배째기에 코드를 곰곰히쳐다보았더랍니다.

문제는 Intel C++ Compiler가 MSVC 호환모드로 작동할때에는 Intel용 컴파일과 다른 식의 설정적용이 되어야 하는데 그게 안되어 있더군요. 훗. MSVC 호환모드면서 Intel C++ Compiler라면 처리를 다르게 해주어야 하는데 말이죠..

그래서 #if 문에 몇가지 조건문을 넣어주니 완벽하게 작동. 눈물을 흘리면서, 기뻐했더랍니다. 하하.

내친김에 WinMerge로 패치파일 만들어서 stlport팀에 보내주었더니 1시간 좀 지나서 바로 적용해주네요. 아이 기분 좋아라. >.<b

5.0.2에는 단 2줄이지만 까막군의 수정사항도 반영될듯. 에헤.

오픈소스 C++ IDE CodeBlocks

CodeBlocks는 오픈소스 크로스플랫폼 C++ IDE입니다.
그토록 갈구하던 녀석이군요. 으흑. ;ㅁ;

특이한 점이랄까.. 하는 것은 여러 C++ Compiler를 지원한다는 점입니다.

  • GCC (MingW / Linux GCC)
  • MSVC++
  • Digital Mars
  • Borland C++ 5.5
  • Open Watcom


지원하는 군요. 어차피 쓰는 것은 MSVC++과 GCC이긴 하겠지만요. 리눅스에서도 작동하고 윈도우에서도 작동합니다. 특히
윈도우 플랫폼에서는 MingW가 기본으로 포함된 버젼도 있군요. 키야앙!!! – MSVC++은 무료로 제공되는 컴파일러가
있지요.. 후후후.

사실 DevC++같은 툴은 모자란 점이 좀 많아서 불만이었는데, 이건 쓰기 편한점이 많은듯 하네요.

아직, auto-completion같은 기능이 좀 모자란 면이 보이긴 하지만, 해결되리라 생각하고…
디버깅기능도 살짝 모자랍니다. local변수 보기같은… (Watch만 되는 것 같더군요)

MSVC++ 프로젝트 임포트도 지원하고. (버그는 좀 있답니다..)

사실 STL/Boost의 auto-completion만 잘 지원되주기만 해도.. 으흐흐.

CodeBlock의 완성도가 높아진다면, 굳이 Visual C++을 이용할 이유가 사라질 것 같기도 합니다. 🙂

boost::shared_ptr에 관해!

boost::shared_ptr은 boost/smart_ptr.hpp를 이용해 사용할 수 있는 라이브러리입니다.

smart_ptr은 그 이름과 같이, smart pointer들을 다루고 있는 라이브러리로, noncopyable(복사 불가능한) auto_ptr인 scoped_ptr과 레퍼런스 카운팅을 이용해 객체 해제를 자동으로 관리해주는 shared_ptr, shared_ptr에 레퍼런스 카운팅을 하지 않고 단순히 참조만 할 수 있게 해주는 weak_ptr, shared_ptr과 유사한 intrusive_ptr이 있으며, 배열 스타일의 포인터 사용을 위한 scoped_array, shared_array 역시 존재합니다.

지금부터 알아볼 클래스는 바로 boost::shared_ptr로 C++ 프로그래머들이 겪는 Memory Leak을 효과적으로 방지하고, 동적으로 할당한 객체들을 컨테이너에서 손쉽게 관리하도록 해주는 매우 유용한 툴입니다.


1. std::auto_ptr의 함정.

뜬금없이 왠 std::auto_ptr을 언급하냐! 라며 울부짖으실 분도 있으시겠지만, 많은 사람들이 저지르는 실수 중 한가지가 auto_ptr과 관련이 있으며, 이에 대한 해결책 중에 한가지가 shared_ptr이기에 짚고 넘어가려고 합니다.

Scott Meyers의 Effective STL 항목8에 잘 나와있듯이, auto_ptr의 컨테이너는 절대로 만들어서는 안됩니다. 오래 살고 싶으시다면요. ^^

먼저 auto_ptr에 대해 잠깐 알아보기로 하지요.

#include <memory>

void test()
{
//먼저 힙에 int를 하나 만듭시다.
int* int_p = new int;
//auto_ptr_int을 만듭니다! 녀석은 int_p를 관리하게 됩니다.
std::auto_ptr<int> auto_ptr_int(int_p);
*auto_ptr_int = 3; //  int_p에 3을 넣습니다.
std::cout << "hehehe in test()" << std::endl;
// 그냥 심심해서 뿌려본겁니다.
return; // 이때 int_p가 파괴됩니다.
}
&#91;/cpp&#93;

위의 예제에서 알 수 있듯이, int_p는 함수가 종료됨과 함께 파괴됩니다. 그 이유는 auto_ptr때문이지요.

auto_ptr은 자신이 파괴될 때, 자신이 소유한 객체를 파괴합니다. 예제에서는 스택에 auto_ptr을 생성했으므로, 스택이 파괴될때 - 즉, 함수가 종료될 때 - auto_ptr이 파괴됩니다.
소유권 이전의 문제도 알아봅시다.

&#91;cpp&#93;
#include <memory>
void test2()
{
std::auto_ptr<int> ap1;
ap1 = new int; // 새로 생성된 int객체에 대한 소유권은 ap1에 있습니다.
{
std::auto_ptr<int> ap2;
ap2 = ap1; // ap1이 갖고 있던 소유권이 ap2에 이전됩니다.
}
// 블럭이 종료되면서 ap2가 파괴됩니다.
// 따라서, int객체도 파괴되지요.
}
// ap1이 파괴되지만, ap1은 어떤 객체도 소유하고 있지 않으므로
// 아무일도 일어나지 않습니다.

auto_ptr은 위에서 알 수 있듯이, 소유권을 공유할 수 있는 방법은 없습니다. 강제적으로 하더라도, 같은 객체를 소유하고 있는 auto_ptr중 어느 하나라도 파괴된다면, 그 즉시 객체가 파괴되어 버리므로, 프로그램은 엉망이 되어버리겠지요.

auto_ptr의 컨테이너를 만들면 안되는 이유도 같은 이유입니다.

#include <memory>
#include <vector>

// C++ 프로그래머의 영원한 친구 typedef!!!!
typedef std::auto_ptr<int> AIP;
void test3()
{
std::vector<aip> aip_vec;
....(aip_vec에 데이터를 마구마구 넣읍시다.)...
AIP ap1 = aip_vec.at(0);
AIP ap2 = aip_vec.at(0);
std::cout << *ap1 << std::endl; // 문제되지 않습니다.
std::cout << *ap2 << std::endl; // segment fault가 발생합니다.
}
&#91;/cpp&#93;

위의 예제에서 ap1은 aip_vec의 첫번째 AIP(std::auto_ptr<int>)가 가진 int 객체에 대한 소유권을 이전받습니다. 즉 aip_vec[0]는 아무 객체도 소유하고 있지 않지요. 따라서 ap2역시 어떤 소유권도 가질 수 없습니다. 이때 ap2를 참조하게 된다면 문제가 발생 하지요.

컨테이너들은 무조건 값에 의해 작동합니다. 참조나 포인터가 아니지요. 따라서, auto_ptr을 컨테이너에 사용하는 것은 매우 위험할 수 있습니다. 알고리즘이나 멤버함수에 따라 객체가 파괴되어버릴 수 있으니까요!!!

<hr/>
<strong>2. 그렇다면 boost::shared_ptr은?</strong>

boost::shared_ptr은 레퍼런스 카운팅을 이용해 작동합니다. 레퍼런스 카운팅이란, 특정 객체에 대해 카운터를 놓고 참조자가 늘어날 때마다 카운트를 올리고, 참조자가 줄어들때마다 카운트를 내리다가, 참조자가 없어지면 그때 객체를 파괴하는 방식입니다.

앞의 test2를 shared_ptr을 이용해 바꾸고 그 동작을 살펴보기로 하지요.

[cpp]
#include <boost/shared_ptr.hpp>
void test4()
{
boost::shared_ptr<int> sp1;
// 카운트가 1이 됩니다. int객체에 대한 shared_ptr은 sp1뿐이니까요.
sp1 = boost::shared_ptr<int>(new int);
{
boost::shared_ptr<int> sp2;
sp2 = sp1; // int객체에 대한 카운트가 2가 됩니다. sp1, sp2 2개니까요.
}
// sp2가 파괴되면서 카운트가 하나 줄어듭니다. 이젠 1이군요.
}
// sp1이 파괴되면서 카운트가 줄어들어 0이 됩니다.
// 이때 int객체가 파괴됩니다. :)

간단하지요? 카운트가 늘어나고 줄어들고, 0이되면 파괴된다는 사실만 기억하시면 됩니다.
그럼 이번엔 좀 더 복잡한 예를 살펴보기로 하지요. vector를 이용한 예제입니다.

#include <boost/shared_ptr.hpp>
typedef boost::shared_ptr<int> SIP
void test5()
{
SIP sp1 = SIP(new int); // 카운트는 1입니다.
std::vector<sip> sip_vec;
sip_vec.push_back(sp1); // 카운트는 2
sip_vec.push_back(sp1); // 카운트는 3
sip_vec.pop_back(); // 카운트는 2
sip_vec.pop_back(); // 카운트는 1
} // 이때 파괴됩니다.

vector는 값에 의한 의미론이므로, push_back 멤버함수는 복사를 통해 작동하게 됩니다. 즉, sip_vec.push_back(sp1)은 sp1의 사본을 sip_vec에 넣는다 라는 의미이지요. sp1은 복사되면서 카운트를 증가시킵니다. -복사연산자, 복사생성자를 통해서요.- 따라서 push_back이 완료되고 나면 카운트는 2가 되지요.

shared_ptr의 강력함중 하나는 컨테이너에서 객체를 삭제할 경우입니다. shared_ptr을 사용하지 않고, 그냥 포인터를 써보기로 하지요.

#include <vector>
class Test; // 있다고 칩시다.
typedef std::vector<test*> TestVectorPTR;
void test6()
{
Test* t = new Test;
TestVectorPTR v;
v.push_back(t);
v.pop_back();
// t가 지워지질 않습니다아..
// 허허. 당연하죠. vector는 값의미론이니까요.
delete t; // 항상 이렇게 손으로 지워줘야 합니다.
}

객체에 대한 포인터를 여러 컨테이너에 넣어두었다고 생각해 봅시다. 그렇다면, 각 컨테이너마다 해당하는 포인터가 존재하는지 죄다 확인해가면서 삭제해야하는 우울함이 발생합니다. 흐흑.

shared_ptr을 써보면..

#include<vector>
#include <boost/shared_ptr.hpp>
class Test; // 역시나 있다고 칩시다.
typedef boost::shared_ptr<test> TSP;
typedef std::vector<tsp> TSPVector;
TSPVector vec;
void test7_init()
{
// vec에 새 객체를 추가합니다. 카운트는 1이지요.
vec.push_back(TSP(new Test));
}

void test7()
{
// init()에서 추가한 객체가 파괴됩니다. 카운트가 0가 되니까요.
vec.pop_back();
}

직접 지워주지 않아도 shared_ptr의 파괴자에서 알아서 객체를 파괴시켜줍니다. 지긋지긋한 메모리누수현상에서 해방이지요. 하핫. 여러 컨테이너에 넣는 경우도 전혀 우울하지 않습니다. 한군데서 지운다고 다른 컨테이너에서 문제가 발생하진 않을테니까요. 카운트가 0가 되는 시점에서 파괴된다는 컨셉은 메모리관리에서 아주 행복한 컨셉이지요.


3. shared_ptr의 파괴동작.

shared_ptr을 단순히 new/delete를 이용해 생성/파괴하는 객체에만 사용할 수 있다는 생각은 크나큰 오산입니다. Custom Destructor를 세팅할 수 있게 해주는 기능은 shared_ptr의 가능성을 대폭 증가시켜 줍니다. 🙂

#include <boost/shared_ptr.hpp>
// 사실 싫어하는 예이지만, HANDLE을 예로 들고 싶기에 어쩔수.. 없;;
#include <windows.h>
typedef boost::shared_ptr<void> HANDLESP;

void test8()
{
HANDLE h = ...(뭐든 핸들을 받아온다)...
HANDLESP hsp = HANDLESP(h, CloseHandle);
}// hsp가 파괴될때 CloseHandle(h)가 호출된다.

hsp를 생성할때 보면, 뒤쪽에 CloseHandle이란 함수를 넣어주는 것을 발견할 수 있습니다. CloseHandle의 위치에는 HANDLE(정확히는 void*)을 매개변수로 받는 호출가능한 C++객체는 무엇이든 올 수 있습니다. 🙂

즉, 객체가 C++ 표준인 new/delete를 이용해 할당되지 않더라도 파괴될때 호출할 호출가능한 객체를 지정해주면, delete대신 그 함수를 통해 객체를 파괴하게 되지요.

DLL 사이에서 객체를 주고 받을 때도 매우 유용합니다. DLL A에서 생성한 객체를 DLL B에서 파괴할 경우 문제가 발생하기 때문에, A의 인터페이스에 객체를 삭제하는 함수를 등록시켜서 쓰는 것이 일반적인데, 이런 경우에도 객체를 삭제하는 함수를 파괴시 호출할 함수로 지정해주면 간단히 shared_ptr을 적용할 수 있는 것이지요. 이때, 전에 설명했던 boost::bind가 큰 힘을 발휘하는 경우가 많답니다. 🙂


4. Tip.

shared_ptr이 가진 좋은 점은, C++ 프로그래머를 할당/해지, 혹은 생성/파괴 관리라는 리소스 누수현상에서 해방시켜준다는 점입니다. 상당히 행복하지요. 🙂
조금 더 행복해 지기 위해서 간단한 팁을 하나 준비했습니다.

class A
{
...(some definitions)...
public:
typedef boost::shared_ptr<a> SP;
}

정말 간단합니다. class를 하나 만들때마다 SP라는 이름으로 shared_ptr을 미리 정의해 주는 것인데요, 매번 boost::shared_ptr<타입이름>을 치기 귀찮으니 미리미리 typedef을 해두자는 간단한 작전입니다. 일단 해보면 실제 작업할때 상당히 편하기도 하고, 타입이름*보단 타입이름::SP를 통해 좀 더 안전한 코드를 미리 작성하게 되는 부수효과도 있답니다-


A. References

shared_ptr 문서
Effective STL, Scott Meyers 저/곽용재 편역, Addison Wesley / 인포북


B. notes

2009년 5월 31일: 워드프레스에 맞게 재편집.
2009년 11월 25일: 스크롤바 문제로 재편집.

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라고 쓰면, 표준에 어긋나더군요. &를 붙여서 함수주소를 넘기는 방법이 정확한 방식입니다.

C++ 서적들.

0. The C++ Programming Language


C++의 창시자인 Bjarne Stroustrup옹이 친히 집필하신 바이블. 최고다. TCPP와 맞먹음.
특정 상황에서 C++의 작동이 궁금하거나, 표준이 궁금하다면 끼고 살아야함. (실제로 끼고 삼)


1. C++ Standard Library 튜토리얼-레퍼런스


조슈티스 책이라고도 불리는 STL에 관련된 최고의 레퍼런스.
적절한 설명과 적절한 팁과 적절한 예제가 잘 범벅이 되어있음.


2. Effective STL/C++, More Effective C++


특히 Effective STL. 정말 유용한 책. Meyers아저씨 원츄.
주로 팁으로 이루어져 있는 것 같지만, 각 항목들이 유기적으로 잘 구성되어 있다. 헤헷.
지하철에서 읽기 좋은 시리즈.


3. C++ 코딩의 정석


Herb아찌랑 Andrei아찌랑 쿵짝쿵짝해서 쓴, 가이드라인. 현재 유행하고 또 권장되는 C++스타일과 유의사항들을 명료하게 볼 수 있는 사전식 책.


4. Modern C++ Design


Template에 대한 새로운 지평. C++ 템플릿을 제대로 쓰려면 이 책은 꼭 읽어봐야 한다.
결과보다는 과정을 중시했다는 느낌의 책.


5. C++ Template Metaprogramming


Boost에 대해 갖게되는 막연한 두려움을 어느정도 해소시켜주는 책.
– 사실 boost는 metaprogramming 말고, 일반적으로 많이 쓸만한 라이브러리를 모아서 정리한 책이 필요하긴 하다.


6. Efficient C++


최근 담당하는 제품 최적화 작업에 들어가면서, 가장 많은 도움이 되는 책. C++최적화 기법들과 기법들을 적용할때 유의할 점들이 정리되어 있다.

7. Design Patterns: Elements of Reusable Object-oriented Software


GoF책이라고 불리우는
명저.(4명의 저자를 일컬어 Gang of Four라고 불렀고.. 줄여서 GoF라고 한다…) C++서적이라고 보기는 힘들수도
있지만, 책의 예제들이 C++로 나온다. 히힛. Design Pattern이 뭔지 알게해준 고마운 책. 원서다.. 으흑. 게다가
제본이다. 조만간 사야겠다… (학부때 가난하던 시절에… ;;;)

etcs..

이거 외에도 몇권 있기는 한데..
Essential C++/Accelerated C++은 이미 알고 있는 내용이었으므로 패스.
C++ 프로그래밍의 이해. / C프로그래머를 위한 C++ (한빛문화사-오라일리) 는 사실 입문서라서 패스. – 정말 입문서다. 내용이 약하다.

ps. 개인적인 생각으로 C++을 MFC로 시작해서 배우는 것 만큼 우울한 것은 없다. MFC는 하나도 안 멋있고, 매혹적이지도 않다. 회사 사장님식 표현에 의하면 “sexy한 스펙이 아니다.”

ps2. 오랜만에 긴거 썼다.

ps3. 사진의 소스는 강컴과 Yes24. 허락받지 않아 죄송하고, 소스제공 ㄳ