이 문서의 목적은 방대한 양의 부스트 라이브러리 중에서 게임 프로젝트에 자주 쓰일만한 유용한 것들을 간단히 소개하는 것이다. 이 문서를 전체적인 가이드로 활용하되 특정 라이브러리를 특정 상황에 쓰게 되는 경우에는 해당 레퍼런스 매뉴얼을 자세히 살펴보아야 실수가 없을 것이다.
문자열 및 텍스트 처리
* conversion/lexical_cast
보통 정수 <-> 문자열 변환을 위해 atoi 표준 함수 및 sprintf를 쓰거나(C 스타일), stringstream(C++ 스타일)을 많이 쓴다. 하지만, 각각 단점을 지니며 간편히 쓰기에는 불편한 점이 많다. 이때 lexical_cast를 쓰면 간단히 변환을 처리할수 있다. 포맷팅과 같은 고급 기능이 요구될 때에는 물론 stringstream을 써야 할 것이다. 다음 예제를 참조한다.
int main(int argc, char * argv[])
{
using boost::lexical_cast;
using boost::bad_lexical_cast;
std::vector<short> args;
while(*++argv)
{
try
{
args.push_back(lexical_cast<short>(*argv));
}
catch(bad_lexical_cast &)
{
args.push_back(0);
}
}
...
}
|
void log_message(const std::string &);
void log_errno(int yoko)
{
log_message("Error " + boost::lexical_cast<std::string>(yoko) + ": " + strerror(yoko));
}
|
* format
우리는 << 연산자가 있음에도 여전히 printf를 자주 쓰게 되는데, 그 이유는 포맷팅 작업의 용이함 때문이다. 하지만 printf는 타입 안정성이 보장되지 않으며(포맷 스트링과 전혀 상관없는 타입을 인자로 주어도 컴파일되며, 인자의 개수가 맞지 않아도 역시 컴파일된다), 또한 여러 언어로 개발 시 필수적인 위치 기반 인자를 지원하지 않는다(가령, 한 언어에서는 첫번째 인자가 먼저 나와야 하지만, 다른 언어에서는 어순이 달라 두번째 인자가 먼저 나와야하는 경우). format은 % 연산자를 사용하여 이러한 제한을 모두 없애면서, 타입 안정성을 유지한다. 다음의 예제를 참고한다(자세한 포맷팅 정보는 매뉴얼을 참조한다. 기존 printf와 호환되는 포맷 문자열은 물론, 위치 기반 포맷팅도 지원한다).
cout << boost::format("writing %1%, x=%2% : %3%-th try") % "toto" % 40.23 % 50;
// prints "writing toto, x=40.230 : 50-th try"
|
format fmter("%2% %1%");
fmter % 36; fmter % 77;
// fmter was previously created and fed arguments, it can print the result :
cout << fmter ;
// You can take the string result :
string s = fmter.str();
// possibly several times :
s = fmter.str( );
// You can also do all steps at once :
cout << boost::format("%2% %1%") % 36 % 77;
// using the str free function :
string s2 = str( format("%2% %1%") % 36 % 77 );
|
// 아래와 같은 절대치 탭 지정 포맷팅도 가능하다. 결과로 전화 번호는 항상 정확히 40번째열에 위치하게 된다.
for(unsigned int i=0; i < names.size(); ++i)
cout << format("%1%, %2%, %|40t|%3%n") % namesi % surnamei % teli;
|
단, 성능상의 페널티가 있으니, 주의하여 사용한다.
* string_algo
기존 STL에 없는 문자열 관련 알고리즘들을 제공한다. 예를 통해 하나씩 살펴보자.
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost;
// ...
string str1(" hello world! ");
to_upper(str1); // str1 == " HELLO WORLD! "
trim(str1); // str1 == "HELLO WORLD!"
string str2=
to_lower_copy(
ireplace_first_copy(
str1,"hello","goodbye")); // str2 == "goodbye world!"
|
위의 예는 문자열 " hello world! "를 대문자로 바꾸고, 앞뒤의 스페이스를 없앤 뒤, "hello"를 "goodbye"로 치환하여 str2로 복사하고 있다.
다음은 단순히 대소문자 전환을 하는 예이다(기존 STL은 한 문자의 대소문자 변환만을 지원하고 있다).
string str1("HeLlO WoRld!");
to_upper(str1); // str1=="HELLO WORLD!"
|
bool is_executable( string& filename )
{
return
iends_with(filename, ".exe") ||
iends_with(filename, ".com");
}
// ...
string str1("command.com");
cout
<< str1
<< is_executable("command.com")? "is": "is not"
<< "an executable"
<< endl; // prints "command.com is an executable"
//..
char text1[]="hello world!";
cout
<< text1
<< all( text1, is_lower() )? "is": "is not"
<< " written in the lower case"
<< endl; // prints "hello world! is written in the lower case"
|
술어구문 및 분류 알고리즘의 예이다(눈치 챘겠지만 접두어 i는 대소문자를 가리지 않음을 뜻한다).
string str1=" hello world! ";
string str2=trim_left_copy(str1); // str2 == "hello world! "
string str3=trim_right_copy(str2); // str3 == " hello world!"
trim(str1); // str1 == "hello world!"
string phone="00423333444";
// remove leading 0 from the phone number
trim_left_if(phone,is_any_of("0")); // phone == "423333444"
|
위는 trim 알고리즘의 예이다.
char text[]="hello dolly!";
iterator_range<char*> result=find_last(text,"ll");
transform( result.begin(), result.end(), result.begin(), bind2nd(plus<char>(), 1) );
// text = "hello dommy!"
to_upper(result); // text == "hello doMMy!"
// iterator_range is convertible to bool
if(find_first(text, "dolly"))
{
cout << "Dolly is there" << endl;
}
|
찾기 알고리즘의 예이다. iterator_range의 등장에 주목하자.
string str1="Hello Dolly, Hello World!"
replace_first(str1, "Dolly", "Jane"); // str1 == "Hello Jane, Hello World!"
replace_last(str1, "Hello", "Goodbye"); // str1 == "Hello Jane, Goodbye World!"
erase_all(str1, " "); // str1 == "HelloJane,GoodbyeWorld!"
erase_head(str1, 6); // str1 == "ane,GoodbyeWorld!"
|
바꾸기 알고리즘을 보여주고 있다.
string str1("abc-*-ABC-*-aBc");
// Find all 'abc' substrings (ignoring the case)
// Create a find_iterator
typedef find_iterator<string::iterator> string_find_iterator;
for(string_find_iterator It=
make_find_iterator(str1, first_finder("abc", is_iequal()));
It!=string_find_iterator();
++It)
{
cout << copy_range<std::string>(*It) << endl;
}
// Output will be:
// abc
// ABC
// aBC
typedef split_iterator<string::iterator> string_split_iterator;
for(string_find_iterator It=
make_split_iterator(str1, first_finder("-*-", is_iequal()));
It!=string_find_iterator();
++It)
{
cout << copy_range<std::string>(*It) << endl;
}
// Output will be:
// abc
// ABC
// aBC
|
찾기 알고리즘을 확장한 찾기 반복자의 예이다. 해당 조건을 만족하는 모든 부분문자열을 찾아서 순환할 수 있게 해준다. find 버전의 split 버전의 차이는 눈치 챘는가? 모르겠다면 자신의 부족한 센스를 탓하며 레퍼런스를 찾아보는 수밖에...
string str1("hello abc-*-ABC-*-aBc goodbye");
typedef vector< iterator_range<string::iterator> > find_vector_type;
find_vector_type FindVec; // #1: Search for separators
ifind_all( FindVec, str1, "abc" ); // FindVec == { [abc],[ABC],[aBc] }
typedef vector< string > split_vector_type;
split_vector_type SplitVec; // #2: Search for tokens
split( SplitVec, str1, is_any_of("-*-") ); // SplitVec == { "hello abc","ABC","aBc goodbye" }
|
해당 조건을 만족하는 부분문자열들을 찾아서, 혹은 해당 문자열로 전체 문자열들을 나누어 컨테이너에 넣어주는 split 알고리즘의 예를 보여주고 있다.
wformat을 이용한다.
wcout << boost::wformat(_T("writing %1%, x=%2% : %3%-th try")) % _T("toto") % 40.23 % 50; |
* tokenizer
문자열을 토큰들로 나누어주는 기능을 제공한다. 어떻게 토큰화할지는 TokenizerFunction으로 지정해줄 수 있다. 다음의 세가지 예를 보면 쉽게 이해할 수 있을 것이다.
// simple_example_1.cpp
#include<iostream>
#include<boost/tokenizer.hpp>
#include<string>
int main()
{
using namespace std;
using namespace boost;
string s = "This is, a test";
tokenizer<> tok(s);
for(tokenizer<>::iterator beg=tok.begin(); beg!=tok.end();++beg)
{
cout << *beg << "\n";
}
}
|
특별히 지정해주지 않는 경우, 공백 및 문장기호를 기준으로 토큰화가 이루어진다.
// simple_example_2.cpp
#include<iostream>
#include<boost/tokenizer.hpp>
#include<string>
int main()
{
using namespace std;
using namespace boost;
string s = "Field 1,\"putting quotes around fields, allows commas\",Field 3";
tokenizer<escaped_list_separator<char> > tok(s);
for(tokenizer<escaped_list_separator<char> >::iterator beg=tok.begin(); beg!=tok.end();++beg)
{
cout << *beg << "\n";
}
}
|
u0010csv(comma separated value) u0010라인을 토큰화하고 있다("" 안의 콤마는 무시된다).
// simple_example_3.cpp
#include<iostream>
#include<boost/tokenizer.hpp>
#include<string>
int main()
{
using namespace std;
using namespace boost;
string s = "12252001";
int offsets[] = {2,2,4};
offset_separator f(offsets, offsets+3);
tokenizer<offset_separator> tok(s,f);
for(tokenizer<offset_separator>::iterator beg=tok.begin(); beg!=tok.end();++beg)
{
cout << *beg << "\n";
}
}
|
위의 예는 12 25 2001로 토큰화된다.
wtokenizer와 같은 UNICODE 버젼이 존재하지 않으므로 다음과 같이 사용한다.
wstring s = _T("This is, a test");
typedef tokenizer<char_delimiters_separator<wchar_t>,
std::wstring::const_iterator,
std::wstring > wtokenizer;
wtokenizer tok(s);
for(wtokenizer::iterator beg=tok.begin(); beg!=tok.end();++beg)
{
wcout << *beg << _T("\n");
} |
컨테이너
* array
설명이 필요없다. vector가 동적 배열이라면, 그의 정적 버전이다. 기존 날 배열을 대체한다고 보면 된다. 다음 표준 후보인 TR1에 이미 포함되었다.
* multi_array
보통 다차원 배열을 이용하기 위해서는 날 배열을 쓰거나, vector<vector<> > 형식을 써야한다. 하지만 전자는 함수 경계에서 크기 정보가 유실되고, 후자는 오버헤드가 크며 효율적이지 못하다. 충분한 크기의 일차원 배열을 선언하고 매크로 등으로 다차원인 것처럼 접근하는 방법도 불편하기는 마찬가지. 이러한 경우를 위해 탄생한 자료구조가 multi_array이다. 다음의 간단한 예로 설명을 대신한다.
#include "boost/multi_array.hpp"
#include <cassert>
int main ()
{
// Create a 3D array that is 3 x 4 x 2
typedef boost::multi_array<double, 3> array_type;
typedef array_type::index index;
array_type A(boost::extents342);
// Assign values to the elements
int values = 0;
for(index i = 0; i != 3; ++i)
for(index j = 0; j != 4; ++j)
for(index k = 0; k != 2; ++k)
Aijk = values++;
// Verify values
int verify = 0;
for(index i = 0; i != 3; ++i)
for(index j = 0; j != 4; ++j)
for(index k = 0; k != 2; ++k)
assert(Aijk == verify++);
return 0;
}
|
* multi_index
한 컨테이너에 대해 여러 가장 방식으로 인덱싱이 필요한 경우, 보통은 해당 컨테이너의 원소들에 대한 포인터를 가지는 또다른 컨테이너를 생성함으로써 문제를 해결하게 된다. 이 경우에 두 컨테이너의 동기화가 문제가 된다. 이러한 경우에 유용한 컨테이너로 한 무리의 원소들에 대해 여러가지 버전의 인덱싱을 가능하게 해준다. 다음에 전형적인 두가지 예가 나와 있다.
struct employee
{
int id;
std::string name;
employee(int id,const std::string& name):id(id),name(name) {}
bool operator<(const employee& e)const{return id<e.id;}
};
|
// define a multiply indexed set with indices by id and name
typedef multi_index_container<
employee,
indexed_by<
// sort by employee::operator<
ordered_unique<identity<employee> >,
// sort by less<string> on name
ordered_non_unique<member<employee,std::string,&employee::name> >
>
> employee_set;
void print_out_by_name(const employee_set& es)
{
// get a view to index #1 (name)
const employee_set::nth_index<1>::type& name_index=es.get<1>();
// use name_index as a regular std::set
std::copy(
name_index.begin(),name_index.end(),
std::ostream_iterator<employee>(std::cout));
}
|
// Wrong: we forgot the & after employee_set::nth_index<1>::type
const employee_set::nth_index<1>::type name_index=es.get<1>()
|
위 예는 employee라는 자료구조를 id가 아닌 이름으로 정렬하여 인덱싱하는 경우를 보여준다. get 템플릿 함수는 인덱스 개체가 아닌 인덱스에 대한 레퍼런스를 리턴하기 때문에 맨 마지막과 같이 & 없이 사용하면 컴파일 오류가 발생함에 유의한다.
typedef std::list<std::string> text_container;
std::string text=
"Alice was beginning to get very tired of sitting by her sister on the "
"bank, and of having nothing to do: once or twice she had peeped into the "
"book her sister was reading, but it had no pictures or conversations in "
"it, 'and what is the use of a book,' thought Alice 'without pictures or "
"conversation?'";
// feed the text into the list
text_container tc;
boost::tokenizer<boost::char_separator<char> > tok
(text,boost::char_separator<char>(" tn.,;:!?'"-"));
std::copy(tok.begin(),tok.end(),std::back_inserter(tc));
|
std::size_t occurrences(const std::string& word)
{
return std::count(tc.begin(),tc.end(),word);
}
|
void delete_word(const std::string& word)
{
tc.remove(word); // scans the entire list looking for word
}
|
이상과 같이 구현하면 특정 단어의 등장 회수를 세거나, 리스트에서 단어를 제거하는데 선형 시간이 걸리게 된다. 하지만 다음과 같이 multi_index를 이용하면 이 두 작업을 로그 시간 내에 할 수 있게 된다.
// define a multi_index_container with a list-like index and an ordered index
typedef multi_index_container<
std::string,
indexed_by<
sequenced<>, // list-like index
ordered_non_unique<identity<std::string> > // words by alphabetical order
>
> text_container;
std::string text=...
// feed the text into the list
text_container tc;
boost::tokenizer<boost::char_separator<char> > tok
(text,boost::char_separator<char>(" tn.,;:!?'"-"));
std::copy(tok.begin(),tok.end(),std::back_inserter(tc));
|
std::size_t occurrences(const std::string& word)
{
// get a view to index #1
text_container::nth_index<1>::type& sorted_index=tc.get<1>();
// use sorted_index as a regular std::set
return sorted_index.count(word);
}
void delete_word(const std::string& word)
{
// get a view to index #1
text_container::nth_index<1>::type& sorted_index=tc.get<1>();
// use sorted_index as a regular std::set
sorted_index.erase(word);
}
|
* pointer container
힙 할당된 개체들의 포인터를 컨테이너에 담을 경우, 예외 안정성을 보장하려면 shared_ptr 등을 사용하여야 한다. 그러나 이 방법은 컨테이너가 개체들에 대한 소유권을 완전히 가지는 경우 최적이 아니게 되며, 포인터에 대한 포인터 참조라는 오버헤드를 피할 수 없게 된다. 이러한 단점 없이 포인터에 대한 컨테이너 역할을 할 수 있도록 만들어진 컨테이너들의 집합이 이 라이브러리이다. 역시 실례를 통해 살펴보도록 하자.
class animal : boost::noncopyable
{
public:
virtual ~animal() {}
virtual void eat() = 0;
// ...
};
class mammal : public animal
{
// ...
};
class bird : public animal
{
// ...
};
class zoo
{
boost::ptr_vector<animal> the_animals;
public:
void add_animal( animal* a )
{
the_animals.push_back( a );
}
};
zoo the_zoo;
the_zoo.add_animal( new mammal("joe") );
the_zoo.add_animal( new bird("dodo") );
|
ptr_vector의 템플릿 인자로 animal*가 아닌 animal을 주고 있음에 주목한다. 마찬가지로 접근 시의 인터페이스도 다음과 같은 직접 접근 방식을 제공한다.
boost::ptr_vector<animal> vec;
vec.push_back( new animal ); // you add it as pointer ...
vec0.eat(); // but get a reference back
typedef std::vector<animal*> std_vec;
std_vec vec;
...
std_vec::iterator i = vec.begin();
(i)->eat(); // '' needed
typedef boost::ptr_vector<animal> ptr_vec;
ptr_vec vec;
ptr_vec::iterator i = vec.begin();
i->eat(); // no indirection needed
|
기본적으로 STL이 제공하는 모든 시퀀스 및 어소시에이티브 컨터이너의 포인터 버전을 제공하고 있지만, 몇몇 차이 나는 부분이 존재한다. 자세한 사항은 역시 매뉴얼 참조하자.
* variant
C 언어의 유산인 union을 C++ 적으로 계승한 자료구조이다. 안전하면, 제너릭하다. STL 벡터가 "다중 값, 단일 타입"의 구조라면 variant는 "다중 타입, 단일 값"의 구조라 할 수 있다. union과 같은 구조가 무엇에 유의할까 싶지만은 잎 노드에만 값을 가지는 트리 노드 자료구조라던가(이 경우, 자식 정보와 실제 데이터의 union이면 유용할 것이다) 때에 따라 단일 값 혹은 또 하나의 표현식을 가져야하는 표현식 자료구조 등에서는 매우 유용할 수 있다(이 두가지 예는 모두 재귀적 variant를 필요로 하는데 이에 대한 사항은 마지막 예에서 확인할 수 있다).
boost::variant< int, std::string > v;
v = "hello";
std::cout << v << std::endl;
std::string& str = boost::get<std::string>(v);
str += " world! ";
std::cout << v << std::endl;
void times_two( boost::variant< int, std::string > & operand )
{
if ( int* pi = boost::get<int>( &v ) )
*pi *= 2;
else if ( std::string* pstr = boost::get<std::string>( &v ) )
*pstr += *pstr;
}
|
get 함수를 사용하면 간편하기는 하지만 times_two 함수에서 보여지듯이 variant가 담고 있는 값의 실제 타입이 무엇일지 미리 알 수 없는 경우에는 switch 구문으로 일일이 구별해주어야 한다. 이러한 경우를 더욱 강력한 visitor 개념이 지원된다. 다음이 그 예이다.
class times_two_visitor
: public boost::static_visitor<>
{
public:
void operator()(int & i) const
{
i *= 2;
}
void operator()(std::string & str) const
{
str += str;
}
};
boost::apply_visitor( times_two_visitor(), v );
|
위와 같이 방문자 클래스를 정의한 후 apply_visitor 함수를 사용하면 된다. 한발 더 나아가 다음과 같이 제너릭하게 정의해버릴 수도 있다.
class times_two_generic
: public boost::static_visitor<>
{
public:
template <typename T>
void operator()( T & operand ) const
{
operand += operand;
}
};
boost::apply_visitor( times_two_generic(), v );
|
또한 다음과 같은 지연 방문의 개념도 지원한다.
std::vector< boost::variant<int, std::string> > vec;
vec.push_back( 21 );
vec.push_back( "hello " );
times_two_generic visitor;
std::for_each(
vec.begin(), vec.end()
, boost::apply_visitor(visitor)
);
|
마지막으로, 재귀적 variant의 문제를 살펴보자.
struct add;
struct sub;
template <typename OpTag> struct binary_op;
typedef boost::variant<
int
, binary_op<add>
, binary_op
> expression; template <typename OpTag> struct binary_op { expression left; // variant instantiated here... expression right; binary_op( const expression & lhs, const expression & rhs ) : left(lhs), right(rhs) { } }; // ...but binary_op not complete until here! |
이는 컴파일이 실패한다. 이러한 경우가 다음과 같이 recursive_wrapper를 활용해야 한다.
typedef boost::variant<
int
, boost::recursive_wrapper< binary_op<add> >
, boost::recursive_wrapper< binary_op<sub> >
> expression;
class calculator : public boost::static_visitor<int>
{
public:
int operator()(int value) const
{
return value;
}
int operator()(const binary_op<add> & binary) const
{
return boost::apply_visitor( calculator(), binary.left )
+ boost::apply_visitor( calculator(), binary.right );
}
int operator()(const binary_op<sub> & binary) const
{
return boost::apply_visitor( calculator(), binary.left )
- boost::apply_visitor( calculator(), binary.right );
}
};
void f()
{
// result = ((7-3)+8) = 12
expression result(
binary_op<add>(
binary_op<sub>(7,3)
, 8
)
);
assert( boost::apply_visitor(calculator(),result) == 12 );
}
|
반복자
* iterators
표준에 부합하는 반복자의 제작을 도와주는 iterator_facade, 기존 반복자 혹은 유사 반복자를 가지고 그와 비슷하나 약간 다른 기능을 가진 반복자를 쉽게 정의할 수 있도록 해주는 iterator_adaptor를 제공한다. 이에 대한 설명 및 예제는 너무 방대하므로 매뉴얼을 참고하도록 한다. 그 밖에도 다음과 같은 여러 특화된 어댑터들을 기정의하여 제공해주고 있다.
* counting_iterator
* filter_iterator
* function_output_iterator
* indirect_iterator
* permutation_iterator
* reverse_iterator
* shared_container_iterator
* transform_iterator
* zip_iterator
자세한 정보는 매뉴얼을 참고한다.
* operators
연산자 오버로딩을 도와주는 유틸리티 성격의 라이브러리이다. + 연산자를 오버로딩하는 경우 += 연산자도 오버로딩해야하고, < 연산자를 정의할 경우, >, >=, <= 연산자들도 서로 연관지어 정의되는 경우가 많다. 이러한 경우 귀찮은 작업을 줄여준다. 간단히 다음 예를 살펴보자.
template <class T>
class point // note: private inheritance is OK here!
: boost::addable< point<T> // point + point
, boost::subtractable< point<T> // point - point
, boost::dividable2< point<T>, T // point / T
, boost::multipliable2< point<T>, T // point * T, T * point
> > > >
{
public:
point(T, T);
T x() const;
T y() const;
point operator+=(const point&);
// point operator+(point, const point&) automatically
// generated by addable.
point operator-=(const point&);
// point operator-(point, const point&) automatically
// generated by subtractable.
point operator*=(T);
// point operator*(point, const T&) and
// point operator*(const T&, point) auto-generated
// by multipliable.
point operator/=(T);
/ point operator(point, const T&) auto-generated
// by dividable.
private:
T x_;
T y_;
};
// now use the point<> class:
template <class T>
T length(const point<T> p)
{
return sqrt(p.x()*p.x() + p.y()*p.y());
}
const point<float> right(0, 1);
const point<float> up(1, 0);
const point<float> pi_over_4 = up + right;
const point<float> pi_over_4_normalized = pi_over_4 / length(pi_over_4);
|
* tokenizer
문자열 및 텍스트 처리 섹션 참조
알고리즘
* minmax
두 값에서 최소, 최대를 구하고자할 때, 표준의 min, max 함수를 쓰면 불필요하게 비교를 두번하게 된다. 마찬가지로 여러 원소들 중에서 최소 최대를 구하고자할 때, 표준의 min_element와 max_element를 연속하여 사용하는 것 역시 비효율적이다. 이러한 문제를 해결하고자 탄생한 라이브러리이다. 다음의 간단한 예로 자세한 설명을 대신한다.
int main()
{
using namespace std;
boost::tuple<int const&, int const&> result1 = boost::minmax(1, 0);
assert( result1.get<0>() == 0 );
assert( result1.get<1>() == 1 );
list<int> L;
generate_n(front_inserter(L), 1000, rand);
typedef list<int>::const_iterator iterator;
pair< iterator, iterator > result2 = boost::minmax_element(L.begin(), L.end());
cout << "The smallest element is " << *(result2.first) << endl;
cout << "The largest element is " << *(result2.second) << endl;
assert( result2.first == std::min_element(L.begin(), L.end());
assert( result2.second == std::max_element(L.begin(), L.end());
}
|
* string_algo
문자열 및 텍스트 처리 섹션 참조
* utility
제목 그대로 짜잘한 여러 유틸리티들을 제공한다. 쓸만한 것들을 차례대로 살펴보자.
먼저 checked_delete와 checked_array_delete 함수 템플릿이 있다. 이 둘은 불완전한 클래스 타입을 delete 식으로 지우게 되는 사태를 방지해준다. 함수 개체 버전인 checked_deleter와 checked_array_deleter 클래스 템플릿도 제공된다.
다음은 next(), prior() 함수 템플릿이다. 간단히 다음 정의와 예로 설명을 대신한다.
template <class T>
T next(T x) { return ++x; }
template <class T, class Distance>
T next(T x, Distance n)
{
std::advance(x, n);
return x;
}
template <class T>
T prior(T x) { return --x; }
template <class T, class Distance>
T prior(T x, Distance n)
{
std::advance(x, -n);
return x;
}
// Usage is simple:
const std::list<T>::iterator p = get_some_iterator();
const std::list<T>::iterator prev = boost::prior(p);
const std::list<T>::iterator next = boost::next(prev, 2);
|
다음은 클래스 noncopyable이다. 흔히 복사 불가능한 개체를 만들기 위해서는 복사 생성자와 대입 연산자를 private으로 선언하는 방식이 쓰인다. 하지만 다음과 같이 간단히 상속을 이용하게 되면 더 명시적이며 이해하기도 쉬울 것이다.
// inside one of your own headers ...
#include <boost/utility.hpp>
class ResourceLadenFileSystem : boost::noncopyable {
...
|
함수 개체 및 고차원 프로그래밍
* bind
boost::bind는 std::bind1st, std::bind2nd 표준 함수를 일반화한 것이다. 임의의 함수 개체, 함수, 함수 포인터, 멤버 함수 포인터를 지원하며, 함수 개체가 result_type, first_argument_type, second_argument_type의 표준 typedef들을 가질 것을 요구하지도 않는다. 여러 예들을 하나씩 살펴보자.
* 함수 및 함수 포인터에 bind 사용하기
int f(int a, int b)
{
return a + b;
}
int g(int a, int b, int c)
{
return a + b + c;
}
|
bind(f, 1, 2) 아무런 인자도 취하지 않고 f(1, 2) 값을 리턴하는 함수 개체를 리턴한다. 마찬가지로, bind(g, 1, 2, 3)()은 g(1, 2, 3)에 해당한다.
일부의 인자에만 선택적으로 바인드하는 것도 가능하다. bind(f, _1, 5)(x)는 f(x, 5)에 해당한다. 여기서 _1 자리매움 인자로 "첫번째 입력 인자로 대체하시오"라는 뜻이다."
비교할 수 있도록, 다음에 표준 라이브러리 도구들을 사용해 같은 연산을 표현해놓았다:
std::bind2nd(std::ptr_fun(f), 5)(x);
// bind covers the functionality of std::bind1st as well:
std::bind1st(std::ptr_fun(f), 5)(x); // f(5, x)
bind(f, 5, _1)(x); // f(5, x)
|
bind는 2개 이상의 인자를 가지는 함수도 처리할 수 있으며, 인자 대체 방식 자체도 더 일반적이다:
bind(f, _2, _1)(x, y); // f(y, x)
bind(g, _1, 9, _1)(x); // g(x, 9, x)
bind(g, _3, _3, _3)(x, y, z); // g(z, z, z)
bind(g, _1, _1, _1)(x, y, z); // g(x, x, x)
|
bind가 취하는 인자는 복사되어 리턴되는 함수 개체가 내부적으로 가지고 있게 된다. 예를 들면, 다음 코드에서:
int i = 5;
bind(f, i, _1);
|
i 값의 사본이 함수 개체로 저장된다. boost::ref 와 boost::cref을 사용하면 함수 개체가 사본이 아닌 개체에 대한 참조를 저장하도록 할 수 있다:
int i = 5;
bind(f, ref(i), _1);
bind(f, cref(42), _1);
|
* 함수 개체에 bind 사용하기
bind는 함수에 국한되지 않고, 임의의 함수 개체를 받아들일 수 있다. 일반적인 경우에는, 생성된 함수 개체의 리턴 타입을 명시적으로 지정해주어야한다:
struct F
{
int operator()(int a, int b) { return a - b; }
bool operator()(long a, long b) { return a == b; }
};
F f;
int x = 104;
bind<int>(f, _1, _1)(x); // f(x, x), i.e. zero
|
함수 개체가 result_type이라는 내부 타입을 제공할 경우에는, 명시적 리턴 타입을 지정하지 않아도 된다:
int x = 8;
bind(std::less<int>(), _1, 9)(x); // x < 9
|
* 멤버 포인터에 bind 사용하기
멤버 함수에 대한 포인터 및 데이터 멤버에 대한 포인터는 함수 개체라 할 수 없다. 왜냐하면 operator()를 지원하지 않기 때문이다. 편의상, bind의 첫번째 인자로 멤버 포인터를 지정할 수 있는데, 이 때에는 boost::mem_fn을 사용하여 멤버 포인터를 함수 개체로 변환한 것처럼 동작하게 된다. 다시 말해, 다음의 식은
다음과 동일하다.
bind<R>(mem_fn(&X::f), args)
|
여기서 R은 X::f 의 리턴 타입이거나(멤버 함수의 경우) 멤버의 타입이다(멤버 데이터의 경우.)
* 중첩된 bind로 함수 합성하기
bind의 인자로 bind 표현식 자체를 중첩하여 줄 수 있다:
bind(f, bind(g, _1))(x); // f(g(x))
|
함수 개체 호출 시에 먼저 안쪽 bind 표현식들이 임의의 순서로 처리된 후, 바깥쪽 bind가 처리된다. 바깥쪽 bind 처리는 안쪽 처리의 결과가 바로 그자리에 대체되어 이루어진다. 위의 예에서, 인자 목록 (x)로 함수 개체를 호출하면, bind(g, _1)(x)가 먼저 처리되어 g(x)가 된 후, bind(f, g(x))(x)가 처리되어, 최종적으로 f(g(x))의 결과를 내게 된다.
이러한 bind의 기능을 활용하여 함수를 합성할 수 있다.
* 오버로딩된 연산자들
편의를 위해, bind가 리턴하는 함수 개체들에서는 논리 부정 연산자 ! 와 관계 연산자들 ==, !=, <, <=, >, >= 를 오버로딩하고 있다.
!bind(f, ...)는 bind( logical_not(), bind(f, ...) )와 같다고 할 수 있는데, 여기서 logical_not은 한 인자 x를 취하여 !x를 리턴하는 함수 개체이다.
op가 관계 연산자라 할 때, bind(f, ...) op x는 bind( relation(), bind(f, ...), x )와 같다고 할 수 있는데, 여기서 relation은 a, b 두 인자를 취하여 a op b를 리턴하는 함수 개체이다.
이상으로 실제 적용 시 편리하게 bind의 결과를 거꾸로 할 수 있다:
std::remove_if( first, last, !bind( &X::visible, _1 ) ); // remove invisible objects
|
또한 bind의 결과를 특정 값에 비교하거나:
std::find_if( first, last, bind( &X::name, _1 ) == "peter" );
|
특정 자리매움 인자와 비교하거나:
bind( &X::name, _1 ) == _2
|
또는 또다른 bind 표현식과 비교하는 것이 가능하다:
std::sort( first, last, bind( &X::name, _1 ) < bind( &X::name, _2 ) ); // sort by name
|
* mem_fn
boost::mem_fn은 std::mem_fun, std::mem_fun_ref 표준 함수를 일반화한 것이다. 이것이 리턴하는 함수 개체는 그 첫번째 인자로 개체 인스턴스에 대한 포인터, 참조, 스마트 포인터를 취할 수 있다.
아래와 같이 기존 알고리즘에 적용하여 사용할 수 있으며,
std::for_each(v.begin(), v.end(), boost::mem_fn(&Shape::draw));
|
다음처럼, 아예 랩핑하여 사용자가 투명하게 사용할 수 있도록 하는 것도 가능하다.
template<class It, class R, class T> void for_each(It first, It last, R (T::*pmf) ())
{
std::for_each(first, last, boost::mem_fn(pmf));
}
for_each(v.begin(), v.end(), &Shape::draw);
|
전술한 것처럼 다음과 같이 사용될 수 있다.
struct X
{
void f();
};
void g(std::vector<X> & v)
{
std::for_each(v.begin(), v.end(), boost::mem_fn(&X::f));
};
void h(std::vector<X *> const & v)
{
std::for_each(v.begin(), v.end(), boost::mem_fn(&X::f));
};
void k(std::vector<boost::shared_ptr<X> > const & v)
{
std::for_each(v.begin(), v.end(), boost::mem_fn(&X::f));
};
|
함수 개체 호출 시, 첫 인자가 적절한 클래스(위 예의 경우 X)에 대한 포인터도 아니고 참조도 아닐 경우, get_pointer(x)를 통해 x로부터 한 포인터를 얻어내게 된다. 따라서 라이브러리 제작자들은 get_pointer를 적절히 오버로딩하여 그들만의 스마트 포인터 클래스를 등록할 수도 있다. 이렇게 되면 자동적으로 mem_fn은 그를 인식하여 지원한다.
* function
boost::function은 지연 호출 또는 콜백을 위한 함수 개체 랩퍼이다. 다음의 여러 예들로 설명을 대신한다.
boost::function<float (int x, int y)> f;
struct int_div {
float operator()(int x, int y) const { return ((float)x)/y; };
};
f = int_div();
std::cout << f(5, 3) << std::endl;
|
boost::function<void (int values[], int n, int& sum, float& avg)> sum_avg;
void do_sum_avg(int values[], int n, int& sum, float& avg)
{
sum = 0;
for (int i = 0; i < n; i++)
sum += valuesi;
avg = (float)sum / n;
}
sum_avg = &do_sum_avg;
|
if (f)
std::cout << f(5, 3) << std::endl;
else
std::cout << "f has no target, so it is unsafe to call" << std::endl;
f = 0;
|
float mul_ints(int x, int y) { return ((float)x) * y; }
f = &mul_ints;
|
struct X {
int foo(int);
};
boost::function<int (X*, int)> f;
f = &X::foo;
X x;
f(&x, 5);
|
stateful_type a_function_object;
boost::function<int (int)> f;
f = boost::ref(a_function_object);
boost::function<int (int)> f2(f);
|
int compute_with_X(X*, int);
f = &X::foo;
assert(f == &X::foo);
assert(&compute_with_X != f);
a_stateful_object so1, so2;
f = boost::ref(so1);
assert(f == boost::ref(so1));
assert(f == so1); // Only if a_stateful_object is EqualityComparable
assert(f != boost::ref(so2));
|
* functional/hash
해쉬 함수 개체이다. 다음과 같은 타입들에 대한 기본 지원된다.
* integers
* floats
* pointers
* strings
* arrays
* std::pair
* the standard containers.
* extending boost::hash for custom types.
기본 지원되는 타입들에 대한 용례는 다음과 같다.
std::unordered_multiset<std::vector<int>, boost::hash<int> >
set_of_ints;
std::unordered_set<std::pair<int, int>, boost::hash<std::pair<int, int> >
set_of_pairs;
std::unordered_map<int, std::string, boost::hash<int> > map_int_to_string;
|
#include <boost/hash/hash.hpp>
int main()
{
boost::hash<std::string> string_hash;
std::size_t h = string_hash("Hash me");
}
|
#include <boost/hash/pair.hpp>
int main()
{
boost::hash<std::pair<int, int> > pair_hash;
std::size_t h = pair_hash(std::make_pair(1, 2));
}
|
template <class Container>
std::vector<std::size_t> get_hashes(Container const& x)
{
std::vector<std::size_t> hashes;
std::transform(x.begin(), x.end(), std::insert_iterator(hashes),
boost::hash<typename Container::value_type>());
return hashes;
}
|
사용자 타입에 대해 확장하는 것도 가능하다. 다음 예를 살펴보자.
namespace library
{
struct book
{
int id;
std::string author;
std::string title;
// ....
};
bool operator==(book const& a, book const& b)
{
return a.id == b.id;
}
}
|
namespace library
{
std::size_t hash_value(book const& b)
{
boost::hash<int> hasher;
return hasher(b.id);
}
}
|
library::book knife(3458, "Zane Grey", "The Hash Knife Outfit");
library::book dandelion(1354, "Paul J. Shanley",
"Hash & Dandelion Greens");boost::hash<library::book> book_hasher;
std::size_t knife_hash_value = book_hasher(knife);
// If std::unordered_set is available:
std::unordered_set<library::book, boost::hash<library::book> > books;
books.insert(knife);
books.insert(library::book(2443, "Lindgren, Torgny", "Hash"));
books.insert(library::book(1953, "Snyder, Bernadette M.",
"Heavenly Hash: A Tasty Mix of a Mother's Meditations"));
assert(books.find(knife) != books.end());
assert(books.find(dandelion) == books.end());
|
만약 book 클래스가 상등성 체크에 단순히 id만이 아닌 저자와 제목도 같이 사용한다면 어떻게 해슁을 해야 할까? 이러한 용도로 boost::hash_combine 이 지원된다. 다음 예를 참고한다.
class point
{
int x;
int y;
public:
point() : x(0), y(0) {}
point(int x, int y) : x(x), y(y) {}
bool operator==(point const& other) const
{
return x == other.x && y == other.y;
}
};
|
class point
{
...
friend std::size_t hash_value(point const& p)
{
std::size_t seed = 0;
boost::hash_combine(seed, p.x);
boost::hash_combine(seed, p.y);
return seed;
}
...
};
|
iterator 범위에 적용되는 boost::hash_range 도 있다.
std::vector<std::string> some_strings;
std::size_t hash = boost::hash_range(some_strings.begin(), some_strings.end());
|
* lambda
lambda는 호출 시점에 이름없는 함수 개체를 정의할 수 있는 기능이다. 감이 오질 않는다면, 다음의 예를 살펴보라.
for_each(a.begin(), a.end(), std::cout << _1 << ' ');
|
위 줄이 무슨 일을 하고 있는지는 감을 잡을 수 있을 것이다. '람다'란 명칭은 함수적 프로그래밍 및 람다 대수학에서 따온 것이다. 이러한 기능이 왜 필요한지 일반적 의미의 람다 표현식이 무엇인지 차근차근 살펴보자.
STL은 제너릭 컨테이너 및 알고리즘 라이브러리라 할 수 있다. 전형적으로 STL 알고리즘은 함수 개체를 통해 컨테이너 원소들을 처리하게 되며, 이러한 함수 개체는 알고리즘의 한 인자로 전달된다.
함수 호출 구문으로 호출가능한 모든 C++ 구성요소는 함수 개체라 할 수 있다. STL에서는 몇몇 흔한 경우에 대한 함수 개체들을 기정의해놓고 있다(plus, less, not1 등). 예를 들면 plus 템플릿은 다음과 같이 구현될 수 있다:
template <class T> : public binary_function<T, T, T>
struct plus {
T operator()(const T& i, const T& j) const {
return i + j;
}
};
|
베이스 클래스 binary_function<T, T, T>에는 함수 개체의 인자 및 리턴 타입들에 대한 typedef가 포함되어 있는데, 이러한 표준 typedef들은 함수 개체를 적응가능토록(adaptable) 만드는데 필요하다.
위와 같은 기본 함수 개체 클래스들과 함께, STL에서는 인자들 중 하나의 값을 상수로 고정함으로써 적응가능한 이항 함수 개체를 단항 함수 개체로 만들어주는 binder 템플릿들을 제공한다. 예를 들면, 다음과 같은 함수 개체 클래스를 작성하는 대신에,
class plus_1 {
int _i;
public:
plus_1(const int& i) : _i(i) {}
int operator()(const int& j) { return _i + j; }
};
|
plus 템플릿과 binder 템플릿 중 하나(bind1st)를 사용하여 같은 기능을 얻어낼 수 있다. 다음의 두 식은 동일한 기능을 하는 함수 개체를 만들어낸다. 즉, 호출될 경우, 함수 개체의 인자에 1을 더한 결과를 둘 모두 리턴한다.
plus_1(1)
bind1st(plus<int>(), 1)
|
다음 예는 컨테이너 내의 모든 원소들에 대해 각각 1을 더하여 표준 출력으로 내보낸다.
transform(a.begin(), a.end(), ostream_iterator<int>(cout),
bind1st(plus<int>(), 1));
|
binder 템플릿을 좀 더 일반적으로 적용할 수 있게 하기 위해, STL에서는 함수에 대한 포인터 또는 참조, 멤버 함수 포인터들을 적응가능하게 만드는 adaptor들을 제공하고 있다.
이러한 모든 도구들의 목적은 단 한가지이다: STL 알고리즘의 호출 시점에 이름없는 함수를 명시할 수 있게 하는 것, 다시 말해, 코드 조각을 함수 인자로 넘길 수 있게 만드는 것이다. 그러나 아직 온전히 이 목적을 달성하지 못하고 있다. 위의 예에서 볼 수 있는 것처럼 기존의 방법은 귀찮고 보고 이해하기도 어렵다. 또한 표준 도구들은 적용에 제약이 많다. 가령, 표준에서는 이항 함수의 한 인자를 지정해줄 수 있는 binder만 제공할뿐, 3항 혹은 4항 함수에 대해서는 제공하고 있지 않다.
부스트 람다 라이브러리는 전술한 문제점들에 대해 해법을 제시한다.
- 직관적인 구문으로 이름없는 함수를 쉽게 만들어낼 수 있다. 위의 예는 다음과 같이 간단해진다.
transform(a.begin(), a.end(), ostream_iterator<int>(cout),
1 + _1);
|
혹은 더 간단하게 다음과 같이 할 수 있다.
for_each(a.begin(), a.end(), cout << (1 + _1));
|
- 인자 바인딩의 제약 대부분이 사라진다. 모든 실제적인 C++ 함수의 임의의 인자들에 대해 바인딩이 가능하다.
- 몇몇 STL 확장에서 지원하고 있는 별도의 함수 합성 연산이 불필요한데, 드러나지 않게 함수 합성이 지원되기 때문이다.
이제 람다 표현식에 대해 좀더 알아보자. 람다 표현식 여러 함수적 프로그래밍 언어(파이썬 포함)에서 흔히 나타나며, 보통 다음과 같은 기본적 형태를 지닌다.
이름없는 함수를 정의하는 람다 표현식은 다음과 같은 요소들로 이루어진다:
* 이 함수의 매개변수들: x1 ... xn
* 매개변수들 x1 ... xn을 가지고 함수값을 계산하는 표현식 e
다음은 간단한 람다 표현식의 예이다.
람다 함수를 적용한다는 것은 형식 매개변수들을 실제 인자로 대치함을 의미한다.
(lambda x y.x+y) 2 3 = 2 + 3 = 5
|
람다 표현식 C++ 버전에서는 lambda x1 ... xn 부분이 없고 형식 매개변수들의 이름이 미리 지정되어 있다. 현재 라이브러리 버전에서는, 그러한 미리 지정된 형식 매개변수들이 셋 존재한다. 자리매움자(placeholder)로 불리는 _1, _2, _3 이 그들이다. 각각 람다 표현식으로 정의되는 함수의 첫번째, 두번째, 세번째를 가리킨다.
예를 들어, 다음 정의를 C++ 식으로 표현하면,
아래와 같이 된다.
따라서, C++에는 람다 표현식에 해당하는 구문 키워드는 존재하지 않는다. 피연산자에 자리매움자를 사용하는 것이 람다 표현식에서의 연산자 호출을 의미하게 되는 것이다. 그러나, 이는 사실 연산자 호출 시에만 해당하는 이야이기이다. 실제 람다 표현식은 함수 호출, 제어 구문, 형변환 등을 포함할 수 있으며, 이 각각에 대해 특별한 구문을 사용해주어야 한다. 가장 중요한 것으로, 함수 호출은 bind 함수로 감싸주어야 한다. 예를 들어, 다음의 람다 표현식을 생각해보자.
C++ 버전에서는 foo(_1, _2)이 아니라, 다음과 같이 해주어야 한다.
이러한 유형의 C++ 람다 표현식을 바인드 표현식이라 부른다. 람다 표현식은 C++ 함수 개체를 정의하며, 함수 적용 구문은 다른 함수 개체 호출과 다를 바 없다. 예를 들어 (_1 + _2)(i, j)와 같은 식이다.
다음의 예제들로 그 편리함을 확인해보는 것으로 이 섹션은 마무리짓고 나머지 자세한 사항은 매뉴얼을 참조하기 바란다.
list<int> v(10);
for_each(v.begin(), v.end(), _1 = 1);
vector<int*> vp(10);
transform(v.begin(), v.end(), vp.begin(), &_1);
int foo(int);
for_each(v.begin(), v.end(), _1 = bind(foo, _1));
sort(vp.begin(), vp.end(), *_1 > *_2);
for_each(vp.begin(), vp.end(), cout << *_1 << 'n');
|
* ref
이미 앞 섹션들에서 몇번 등장했던 놈이다. 값으로 인자를 받는 함수 템플릿에 참조로 인자를 넘기고자 할 때 유용하다. boost::reference_wrapper<T> 클래스 템플릿을 정의하고 있으며, boost::ref 와 boost::cref 두 함수로 그 인스턴스를 생성하게 된다. 그 외에 boost::is_reference_wrapper<T> 와boost::unwrap_reference<T>의 두 특성 클래스도 정의하고 있다.
boost::reference_wrapper<T>는 T 타입의 오브젝트에 대한 참조를 가지며 값으로 인자를 취하는 함수 템플릿에 레퍼런스를 전달하는 역할을 하게 된다. 식 boost::ref(x)는, X가 x의 타입이라고 할 때, boost::reference_wrapper<X>(x) 를 리턴하며, 마찬가지로 boost::cref(x)는 boost::reference_wrapper<X const>(x) 를 리턴한다.
* signals
signal 및 slot 개념을 통해 이벤트 시스템을 제공한다. C++/CLI에서의 event/delegate 개념과 동일하다. signal은 하나의 이벤트를 나타내면 하나의 signal에 여럿의 slot들을 등록할 수 있다. slot들은 이벤트 타겟 혹은 구독자라 볼 수 있다. boost::function이 하나의 함수 개체를 랩핑하는 반면, signal은 여럿의 함수 개체들을 랩핑한다는 점에서, C++/CLI의 delegate 개념과 완전히 동일하다고 볼 수 있다. 예를 통해 살펴보자.
struct Hello
{
void operator()() const
{
std::cout << "Hello";
}
};
struct World
{
void operator()() const
{
std::cout << ", World!" << std::endl;
}
};
boost::signal<void ()> sig;
sig.connect(Hello());
sig.connect(World());
sig(); // Hello, World!
|
기본적으로 FIFO 순서로 처리가 되는데, 정수 기반의 그룹 색인을 지정하여 처리 순서를 제어할 수도 있다.
boost::signal<void ()> sig;
sig.connect(1, World());
sig.connect(0, Hello());
sig();
struct GoodMorning
{
void operator()() const
{
std::cout << "... and good morning!" << std::endl;
}
};
sig.connect(GoodMorning());
// Hello, World!
// ... and good morning! |
다음은 slot이 인자를 받는 경우의 예이다.
void print_sum(float x, float y)
{
std::cout << "The sum is " << x+y << std::endl;
}
void print_product(float x, float y)
{
std::cout << "The product is " << x*y << std::endl;
}
void print_difference(float x, float y)
{
std::cout << "The difference is " << x-y << std::endl;
}
void print_quotient(float x, float y)
{
std::cout << "The quotient is " << x/y << std::endl;
}
boost::signal<void (float, float)> sig;
sig.connect(&print_sum);
sig.connect(&print_product);
sig.connect(&print_difference);
sig.connect(&print_quotient);
sig(5, 3);
// The sum is 8
// The difference is 2
// The product is 15
// The quotient is 1.66667
|
slot이 리턴값을 가진다면 어떻게 될까? 맨 마지막 슬롯의 리턴 값을 최종 리턴하게 할 수도 있고(기본 행동), combiner를 제작하여 리턴 값들 중 최대값을 리턴하게 한다거나 리턴 값 전체를 컨테이너에 담아 리턴하게 하도록 할 수도 있다.
float product(float x, float y) { return x*y; }
float quotient(float x, float y) { return x/y; }
float add(float x, float y) { return x+y; }
float difference(float x, float y) { return x-y; }
boost::signal<float (float x, float y)> sig;
sig.connect();
sig.connect("ient);
sig.connect(&add);
sig.connect(&difference);
std::cout << sig(5, 3) << std::endl; // 2
|
template<typename T>
struct maximum
{
typedef T result_type;
template<typename InputIterator>
T operator()(InputIterator first, InputIterator last) const
{
// If there are no slots to call, just return the
// default-constructed value
if (first == last)
return T();
T max_value = *first++;
while (first != last) {
if (max_value < *first)
max_value = *first;
++first;
}
return max_value;
}
};
boost::signal<float (float x, float y),
maximum<float> > sig;
sig.connect("ient);
sig.connect(&product);
sig.connect(&add);
sig.connect(&difference);
std::cout << sig(5, 3) << std::endl; // 15
|
template<typename Container>
struct aggregate_values
{
typedef Container result_type;
template<typename InputIterator>
Container operator()(InputIterator first, InputIterator last) const
{
return Container(first, last);
}
};
boost::signal<float (float, float),
aggregate_values<std::vector<float> > > sig;
sig.connect("ient);
sig.connect(&product);
sig.connect(&add);
sig.connect(&difference);
std::vector<float> results = sig(5, 3);
std::copy(results.begin(), results.end(),
std::ostream_iterator<float>(cout, " ")); // 15 8 1.6667 2
|
다음은 연결 관리에 대한 예이다. 연결을 아예 끊지 않고 잠시 block 시켜 놓을 수 있음에 주목한다.
boost::signals::connection c = sig.connect(HelloWorld());
if (c.connected()) {
// c is still connected to the signal
sig(); // Prints "Hello, World!"
}
c.disconnect(); // Disconnect the HelloWorld object
assert(!c.connected()); c isn't connected any more
sig(); // Does nothing: there are no connected slots
|
boost::signals::connection c = sig.connect(HelloWorld());
sig(); // Prints "Hello, World!"
c.block(); // block the slot
assert(c.blocked());
sig(); // No output: the slot is blocked
c.unblock(); // unblock the slot
sig(); // Prints "Hello, World!"
|
boost::signals::scoped_connection을 이용하면 특정 스코프 내에서만 임시적으로 유지되는 연결을 구성할 수도 있다.
{
boost::signals::scoped_connection c = sig.connect(ShortLived());
sig(); // will call ShortLived function object
}
sig(); // ShortLived function object no longer connected to sig
|
다음과 같이 시그널에서 특정 함수 개체와 같은 슬롯만 연결 해제할 수도 있다.
void foo();
void bar();
signal<void()> sig;
sig.connect(&foo);
sig.connect(&bar);
// disconnects foo, but not bar
sig.disconnect(&foo);
|
signal 타입에 정의된 slot_type 타입으로 아래와 같이 슬롯을 인자로 넘길 수 있다.
class Button
{
typedef boost::signal<void (int x, int y)> OnClick;
public:
void doOnClick(const OnClick::slot_type& slot);
private:
OnClick onClick;
};
void Button::doOnClick(
const OnClick::slot_type& slot
)
{
onClick.connect(slot);
}
void printCoordinates(long x, long y)
{
std::cout << "(" << x << ", " << y << ")n";
}
void f(Button& button)
{
button.doOnClick(&printCoordinates);
}
|
종합적인 예로 GUI 프로그래밍에서 흔히 보게되는 Document-View 구조를 아래와 같이 구현해볼 수 있다.
class Document
{
public:
typedef boost::signal<void (bool)> signal_t;
typedef boost::signals::connection connection_t;
public:
Document()
{}
connection_t connect(signal_t::slot_function_type subscriber)
{
return m_sig.connect(subscriber);
}
void disconnect(connection_t subscriber)
{
subscriber.disconnect();
}
void append(const char* s)
{
m_text += s;
m_sig(true);
}
const std::string& getText() const
{
return m_text;
}
private:
signal_t m_sig;
std::string m_text;
};
class View
{
public:
View(Document& m)
: m_document(m)
{
m_connection = m_document.connect(boost::bind(&View::refresh, this, _1));
}
virtual ~View()
{
m_document.disconnect(m_connection);
}
virtual void refresh(bool bExtended) const = 0;
protected:
Document& m_document;
private:
Document::connection_t m_connection;
};
class TextView : public View
{
public:
TextView(Document& doc)
: View(doc)
{}
virtual void refresh(bool bExtended) const
{
std::cout << "TextView: " << m_document.getText() << std::endl;
}
};
class HexView : public View
{
public:
HexView(Document& doc)
: View(doc)
{}
virtual void refresh(bool bExtended) const
{
const std::string& s = m_document.getText();
std::cout << "HexView:";
for (std::string::const_iterator it = s.begin(); it != s.end(); ++it)
std::cout << ' ' << std::hex << static_cast<int>(*it);
std::cout << std::endl;
}
};
int main(int argc, char* argv[])
{
Document doc;
TextView v1(doc);
HexView v2(doc);
doc.append(argc == 2 ? argv1 : "Hello world!");
return 0;
}
|
GUI 등에서의 이벤트 모델이나 옵저버 패턴을 구현하는데 매우 유용한 도구라 하겠다.
Comments (0)