라고 제목을 써두면, Spam대처법에 대한 역사적이고 보편적인 History에 대한 글인 것 같지만 사실은 이전에 쓰던 블로그인 PlayAction2와 현재 사용하는 WordPress의 대처법에 대한 이야기이다. 더 보기 “Spam 대처법. 과거와 현재.”
[카테고리:] Development Note
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 변경사항.”
Cut and Paste. 이젠 XV신공!
학부시절 유행하던 말이 있었다. 이른바 “CV신공”.
쫓기는 기말때에 어김없이 몰아치는 텀프로젝트의 폭풍우 앞에서 가련한 학부생들은 째다 붙이기라는 기술의 연마를 통해 학점을 딸 수 밖에 없었다. 그리하야 컨트롤키와 C, V키가 닳아서 없어질 정도로(?) Ctrl-C / Ctrl-V 를 눌러댔다. 이래서 생긴 말이 CV신공.
어느덧 시간이 흘러, 회사에 입사하고 열심히 코드를 작성하다 보니 언제부터인가 코드를 복사해서 붙이는 것이 아닌, 코드를 잘라서 붙여놓고 다시 가져다 쓰는 형태로 작업이 바뀌어 있었다. 이제 CV신공이 아니라 Ctrl-X / Ctrl-V를 반복해서 사용하는 XV신공이라 부름직 하다.
Refactoring이라고 부르는 멋들어진 단어를 갖다 붙이지 않더라도, 우선 XV신공은 대단한 위력을 지닌다. 코드의 볼륨을 키우지 않고도 재사용성을 높일 뿐 아니라, 문제가 생겼을 때 이를 추적하고 수정하는 일 역시 간단하게 바꾸어 주는 것이다.
XV신공의 진가는 이 기술을 시전하다 보면, 언제 시전할지 예측이 되며 예측이 될때 미리 시전해 둔다는 점이다. 미리미리 해두면, 코드의 작성 시간은 점점 줄어들며 이를 반복하다 보면 어느새인가 쓸만한 라이브러리들이 만들어져 있다. XV신공에 의해 체득되는 예측력은 이를 가능하게 하는 원동력이다.
또한, STL을 사용해 작업을 하다 보면 컨테이너에 루프를 돌거나 특정 값을 찾아서 어떤 일을 해야하는 경우가 많은데 이런 경우에 XV신공은 더더욱 위력을 발휘한다. STL의 알고리즘을 사용하게 되면, 특정 비교 작업이나 특정 처리 작업들을 자연스럽게 함수자/함수/멤버함수의 형태로 만들게 되고, 이는 매번 루프를 타이핑 하는 양을 획기적으로 줄여준다.
CV신공이 그 코드량을 빠르고 편하게 늘려 거대한 괴물을 양산하는 완성품 생산형 체제라면, XV신공은 코드의 증가량을 최대한 억제하면서 복잡하지 않은 코드를 만들고 코드가 재사용될 확률을 높이는 부품 생산형 체제라고 볼 수 있다.
XV신공을 사용하게 될 경우, 대부분 잘라낸 코드는 함수형태로 만들게 된다. 이 시점부터 함수의 이름이 엄청나게 중요해진다. 함수의 이름을 잘 지어야, 코드의 가독성이 올라간다. 이름을 잘 짓지 못하면, 차라리 CV신공이 나을 수도 있다. – 이름은 적당히 길어야 좋다. 에디터의 자동완성 기능을 믿자. –
XV신공에 의해 생성된 함수-실행단위들은 여러가지 부가적인 효과들을 가져온다. 특정 로직의 진입점에 대한 제어가 명확해지므로 동시성의 처리가 편해진다. 이미 기 작성된 로직과 같은 기능을 하는 다른 로직을 테스트해보거나 옵션 처리로 작업을 시켜야 할때도 역시 편하다. 최적화를 시켜야할 포인트를 찾는 것 역시 편해진다. 적절히 잘라진 함수-실행단위들은 프로파일러들을 통해 수행시간을 측정할 수 있으며, 이는 CV에 의해 생성된 거대한 코드를 디버깅하는 것과는 차원이 다르다고 할 수 있다. 범위가 좁혀지는 것 만으로도 수사망은 더 확고해 지기 마련이므로.
프로젝트 마감시일이 급박한데 어찌 여유있게 저런 일을 할 수 있느냐고 생각할 수 있지만, 결국은 버릇이다. 습관화된 XV신공은 CV신공보다 요구하는 시간이 그리 크지 않다. 대신 얻는 것은 눈에 보이듯 뻔한 일이다. 편한게 좋은 것 아니겠는가. 하하.
ps. C++에는 inline이란 아주 멋들어진 녀석이 있어서, 함수호출비용 같은건 머릿속에서 날려버린지 오래랍니다. 키힛.
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줄이지만 까막군의 수정사항도 반영될듯. 에헤.
Element+
회사 일로 Xerces C++을 사용하는 중입니다만.. boost와 STL에 찌든 저에게 저런 네이밍은 마음에 안듭니다. 사용법도 복잡하구요.. -_-;
대체물을 물색하다가 libxml2가 떠오르더군요. 지금 이 블로그에서 XML/XSLT 처리기로 사용하고 있는 것이 libxml2/libxslt의 python 바인딩이기도 하고 해서, 잠깐 고려를 해보았습니다. C더군요.. OTL..
Xerces C++을 변경해서 새로운 C++ wrapper를 만들어볼까 했지만, 그다지 마음에 들지도 않고.. 오히려 libxml2가 xpath같은 걸 쉽게 쓸 수 있기도 하고 해서 C++ Wrapper를 만들어보자! 라는 생각이 들었습니다. 몇가지 실험을 해본 결과 만족스럽더군요. operator[ ]를 오버로딩해서 xpath expression을 node에서 바로 쓸 수 있게도 해보고, child를 iterator로 사용할 수 있게도 해보고, iterator도 element만 돌기, text만 돌기 같은 것도 만들어 보구요. 🙂
해서 이름을 붙이려고 하는데, libxml++은 이미 있더군요. (역시나 사용하는 스타일은 매우 마음에 안듭니다.)
사실 libxml2에 있는 기능을 모두 지원할 생각도 없고, xpath를 위주로 한 라이브러리가 될 것 같다는 생각에, Element+라는 이름을 지어보았습니다. 헤헤.
쉬운게 좋은거죠. 히힛.
다만, xpath에 의해 평가된 결과물을 더 쉽게 접근할 수 있는 방법은 좀 더 고민해봐야 할 것 같다는..
목표는 이런거죠 뭐.
element_plus::xpath_result res = doc.eval(“//text()”);
std::for_each(res.begin(), res.end(), std::ptr_fun(print_text_node));
smart tag 처리기가 [ ]를 지원못해서 어쩔수 없이 eval로… ㅡㅜ
요런 스타일로 쓰게 해주는. 헤헤.
이름도 지어놨고, 만드는 일만 남았네요. 아자!
오픈소스 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가 파괴됩니다. } [/cpp] 위의 예제에서 알 수 있듯이, int_p는 함수가 종료됨과 함께 파괴됩니다. 그 이유는 auto_ptr때문이지요. auto_ptr은 자신이 파괴될 때, 자신이 소유한 객체를 파괴합니다. 예제에서는 스택에 auto_ptr을 생성했으므로, 스택이 파괴될때 - 즉, 함수가 종료될 때 - auto_ptr이 파괴됩니다. 소유권 이전의 문제도 알아봅시다. [cpp] #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가 발생합니다. } [/cpp] 위의 예제에서 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) ); [/cpp] 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. 허락받지 않아 죄송하고, 소스제공 ㄳ