Header

  1. View current page

    all2one님의 노트

Profile_img_60x60_01
1

간추린 Boost 라이브러리 2부

 이 문서의 목적은 방대한 양의 부스트 라이브러리 중에서 게임 프로젝트에 자주 쓰일만한 유용한 것들을 간단히 소개하는 것이다. 이 문서를 전체적인 가이드로 활용하되 특정 라이브러리를 특정 상황에 쓰게 되는 경우에는 해당 레퍼런스 매뉴얼을 자세히 살펴보아야 실수가 없을 것이다.

제너릭 프로그래밍

* operators
반복자 섹션 참조

* static_assert
흔히 말하는 컴파일-타임 단언 기능이다. 네임스페이스, 클래스, 함수 범위 내에서 또는 템플릿 안에서 사용이 가능하다. 다음 예제들로 설명을 대신한다.

#include <climits>
#include <cwchar>
#include <boost/static_assert.hpp>

namespace my_conditions {

BOOST_STATIC_ASSERT(sizeof(int) * CHAR_BIT >= 32);
BOOST_STATIC_ASSERT(WCHAR_MIN >= 0);

} // namespace my_conditions

 

#include <iterator>
#include <boost/static_assert.hpp>
#include <boost/type_traits.hpp>

template <class RandomAccessIterator >
RandomAccessIterator foo(RandomAccessIterator from, RandomAccessIterator to)
{
// this template can only be used with
// random access iterators...
typedef typename std::iterator_traits< RandomAccessIterator >::iterator_category cat;
BOOST_STATIC_ASSERT((boost::is_convertible<cat, const std::random_access_iterator_tag&>::value));
//
// detail goes here...
return from;
}

 

#include <climits>
#include <boost/static_assert.hpp>

template <class UnsignedInt>
class myclass
{
private:
BOOST_STATIC_ASSERT(sizeof(UnsignedInt) * CHAR_BIT >= 16);
BOOST_STATIC_ASSERT(std::numeric_limits<UnsignedInt>::is_specialized
&& std::numeric_limits<UnsignedInt>::is_integer
&& !std::numeric_limits<UnsignedInt>::is_signed);
public:
/* details here */
};



* type_traits
부스트 type-traits 라이브러리는 매우 특정한 특성 클래스들의 집합을 제공하며, 각 특성 클래스들은 C++ 타입 시스템에서 단일 특성을 캡슐화한다. 예를 들면, 어떤 타입이 포인터 타입인지 레퍼런스 타입인지 묻는다던가, 특정 타입이 사소한(trivial) 생성자를 가지는지 또는 const 수식자를 가지고 있는지 등을 물을 수 있다.
type-traits 클래스들은 일관된 설계를 공유하는데, 특정 타입이 해당 특성을 가질 경우 특성 클래스는 true_type 타입으로부터 상속받게되고, 그렇지 않으면 false_type으로부터 상속받게되는 점이 그것이다.
또한 type-traits 라이브러리에는 한 타입에 특정한 변환을 행하는 여러 클래스들을 제공한다. 가령, 최상위 레벨의 const 혹은 volatile 수식어를 한 타입으로부터 제거할 수 있다. 변환을 수행하는 각 클래스들은 변환의 결과가 되는 typedef-멤버 type을 정의하는 방식으로 리턴값을 넘겨준다.
'''템플릿 메타프로그래밍 시 매우 유용하게 활용되는 라이브러리이다.''' 자세한 사항은 매뉴얼을 참고한다.


템플릿 메타프로그래밍

* mpl
부스트 메타프로그래밍 라이브러리. 너무 방대하므로 자세한 설명은 생략한다. 자세하고 친절한 정보를 "C++ Template Metaprogramming : Boost로부터 배우는 개념, 도구, 기법" 서적에서 찾을 수 있다.

* static_assert
제너릭 프로그래밍 섹션 참조

* type_traits
제너릭 프로그래밍 섹션 참조


전처리기 메타프로그래밍


병렬 프로그래밍

* thread
플랫폼 독립적인 C++ 멀티쓰레딩 라이브러리. 공부해야할 것, 그리고 주의해야할 것이 많지만, 다음의 간단한 예제로 설명을 대신한다.

class counter
{
public:
// Doesn't need synchronization since there can be no references to *this
// until after it's constructed!
explicit counter(int initial_value)
: m_value(initial_value)
{
}
// We only need to synchronize other for the same reason we don't have to
// synchronize on construction!
counter(const counter& other)
{
boost::mutex::scoped_lock scoped_lock(other.m_mutex);
m_value = other.m_value;
}
// For assignment we need to synchronize both objects!
const counter& operator=(const counter& other)
{
if (this == &other)
return *this;
boost::mutex::scoped_lock lock1(&m_mutex < &other.m_mutex ? m_mutex : other.m_mutex);
boost::mutex::scoped_lock lock2(&m_mutex > &other.m_mutex ? m_mutex : other.m_mutex);
m_value = other.m_value;
return *this;
}
int value() const
{
boost::mutex::scoped_lock scoped_lock(m_mutex);
return m_value;
}
int increment()
{
boost::mutex::scoped_lock scoped_lock(m_mutex);
return ++m_value;
}
private:
mutable boost::mutex m_mutex;
int m_value;
};



게임 내에 멀티쓰레딩을 고려한다면 사용해볼만할듯...


수학 및 수치 관련

* numeric/conversion
최적화된 정책 기반 수치 변환 라이브러리. 역시 예제들로 설명을 대신한다.

#include <iostream>

#include <boost/numeric/conversion/bounds.hpp>
#include <boost/limits.hpp>

int main() {

std::cout << "numeric::bounds versus numeric_limits example.n";

std::cout << "The maximum value for float:n";
std::cout << boost::numeric::bounds<float>::highest() << "n";
std::cout << std::numeric_limits<float>::max() << "n";

std::cout << "The minimum value for float:n";
std::cout << boost::numeric::bounds<float>::lowest() << "n";
std::cout << -std::numeric_limits<float>::max() << "n";

std::cout << "The smallest positive value for float:n";
std::cout << boost::numeric::bounds<float>::smallest() << "n";
std::cout << std::numeric_limits<float>::min() << "n";

return 0;
}

 

#include <boost/numeric_cast.hpp>
#include <iostream>

int main()
{
using boost::numeric_cast;
using boost::bad_numeric_cast;
using boost::positive_overflow;
using boost::negative_overflow;
try {
int i=42;
short s=numeric_cast<short>(i);
}
catch(negative_overflow& e) {
std::cout << e.what();
}
catch(positive_overflow& e) {
std::cout << e.what();
}

try {
float f=-42.1234;
// This will cause a boost::negative_underflow exception to be thrown
unsigned int i=numeric_cast<unsigned int>(f);

double d=f+numeric_cast<double>(i);

unsigned long l=std::numeric_limits<unsigned long>::max();
// This works, because unsigned integral types cannot cause overflow.
unsigned char c=numeric_cast<unsigned char>(l);

unsigned short us=std::numeric_limits<unsigned short>::max();
// This will cause an positive_overflow exception to be thrown
short s=numeric_cast<short>(us);

}
catch(bad_numeric_cast& e) {
std::cout << e.what();
}

return 0;
}



* integer
<boost/cstdint.hpp>에서는 1999 C 표준 헤더 <stdint.h>에 기반한 typedef들을 boost 네임스페이스 내에 제공한다. '''int8_t, int16_t, int32_t, uint8_t, uint16_t, uint32_t 와 같은 typedef들을 사용하면 플랫폼 독립적인 코드를 짜는데 도움이 될 것이다.'''

* multi_array
컨테이너 섹션 참조

* operators
반복자 섹션 참조

* random
무작위 수 생성 라이브러리. 여럿의 무작위 수 생성기(GPG4권의 mersenne twister 포함)를 제공하며, 아울러 무작위 수 분포 개념을 지원하여 여럿의 무작위 수 분포도 클래스들을 제공하고 있다. 이 둘을 조합하여 다양한 확률 상황을 시뮬레이션할 수 있다. 자세한 설명은 역시 매뉴얼을 참조하고, 여기서는 다음의 예제 코드로 설명을 대신한다.

// boost random_demo.cpp profane demo
//
// Copyright Jens Maurer 2000
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// $Id: random_demo.cpp,v 1.17 2005/05/10 20:40:59 jmaurer Exp $
//
// A short demo program how to use the random number library.

#include <iostream>
#include <fstream>
#include <ctime> // std::time

#include <boost/random/linear_congruential.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/variate_generator.hpp>

// Sun CC doesn't handle boost::iterator_adaptor yet
#if !defined(__SUNPRO_CC) || (__SUNPRO_CC > 0x530)
#include <boost/generator_iterator.hpp>
#endif

#ifdef BOOST_NO_STDC_NAMESPACE
namespace std {
using ::time;
}
#endif

// This is a typedef for a random number generator.
// Try boost::mt19937 or boost::ecuyer1988 instead of boost::minstd_rand
typedef boost::minstd_rand base_generator_type;

// This is a reproducible simulation experiment. See main().
void experiment(base_generator_type & generator)
{
// Define a uniform random number distribution of integer values between
// 1 and 6 inclusive.
typedef boost::uniform_int<> distribution_type;
typedef boost::variate_generator<base_generator_type&, distribution_type> gen_type;
gen_type die_gen(generator, distribution_type(1, 6));

#if !defined(__SUNPRO_CC) || (__SUNPRO_CC > 0x530)
// If you want to use an STL iterator interface, use iterator_adaptors.hpp.
// Unfortunately, this doesn't work on SunCC yet.
boost::generator_iterator<gen_type> die(&die_gen);
for(int i = 0; i < 10; i++)
std::cout << *die++ << " ";
std::cout << 'n';
#endif
}

int main()
{
// Define a random number generator and initialize it with a reproducible
// seed.
// (The seed is unsigned, otherwise the wrong overload may be selected
// when using mt19937 as the base_generator_type.)
base_generator_type generator(42u);

std::cout << "10 samples of a uniform distribution in [0..1):n";

// Define a uniform random number distribution which produces "double"
// values between 0 and 1 (0 inclusive, 1 exclusive).
boost::uniform_real<> uni_dist(0,1);
boost::variate_generator<base_generator_type&, boost::uniform_real<> > uni(generator, uni_dist);

std::cout.setf(std::ios::fixed);
// You can now retrieve random numbers from that distribution by means
// of a STL Generator interface, i.e. calling the generator as a zero-
// argument function.
for(int i = 0; i < 10; i++)
std::cout << uni() << 'n';

// Change seed to something else.
//
// Caveat: std::time(0) is not a very good truly-random seed. When
// called in rapid succession, it could return the same values, and
// thus the same random number sequences could ensue. If not the same
// values are returned, the values differ only slightly in the
// lowest bits. A linear congruential generator with a small factor
// wrapped in a uniform_smallint (see experiment) will produce the same
// values for the first few iterations. This is because uniform_smallint
// takes only the highest bits of the generator, and the generator itself
// needs a few iterations to spread the initial entropy from the lowest bits
// to the whole state.
generator.seed(static_cast<unsigned int>(std::time(0)));

std::cout << "nexperiment: roll a die 10 times:n";

// You can save a generator's state by copy construction.
base_generator_type saved_generator = generator;

// When calling other functions which take a generator or distribution
// as a parameter, make sure to always call by reference (or pointer).
// Calling by value invokes the copy constructor, which means that the
// sequence of random numbers at the caller is disconnected from the
// sequence at the callee.
experiment(generator);

std::cout << "redo the experiment to verify it:n";
experiment(saved_generator);

// After that, both generators are equivalent
assert(generator == saved_generator);

// as a degenerate case, you can set min = max for uniform_int
boost::uniform_int<> degen_dist(4,4);
boost::variate_generator<base_generator_type&, boost::uniform_int<> > deg(generator, degen_dist);
std::cout << deg() << " " << deg() << " " << deg() << std::endl;

#ifndef BOOST_NO_OPERATORS_IN_NAMESPACE
{
// You can save the generator state for future use. You can read the
// state back in at any later time using operator>>.
std::ofstream file("rng.saved", std::ofstream::trunc);
file << generator;
}
#endif
// Some compilers don't pay attention to std:3.6.1/5 and issue a
// warning here if "return 0;" is omitted.
return 0;
}



각각 생성기 및 분포도 클래스의 성능도 나와있으니 참고바란다. 기존 표준 라이브러리의 srand() 및 rand()보다 이것들 중 적절한 놈 하나를 쓸 것을 강추하는 바이다.

* uBLAS
uBLAS는 행렬에 대한 BLAS 레벨 1, 2, 3 기능을 제공하는 C++ 템플릿 클래스 라이브러리이다. C++의 연산자 오버로딩을 활용하여 수학적 표기를 단일화하고 있으며, 표현식 템플릿을 통해 포트란에 버금가는 매우 효율적인 코드를 생성해낸다. 자세한 설명은 매뉴얼을 참조하도록 하자.
게임 툴 등에서 대규모의 선형 시스템을 풀어야 하는 상황이 있을 경우, 유용하게 쓰일 수 있을 듯하다.


정확성 및 테스트

* static_assert
제너릭 프로그래밍 섹션 참조

* test
테스팅과 관련된 다양한 도구들을 제공한다. 일단 그 중 TDD의 바탕이 될 단위 테스트 프레임ㅤㅇㅝㅋ부터 살펴보자. 부스트 문서는 다소 번잡하고 방대하므로, Games from Within의 관련 글을 기준으로 간략히 설명하고 넘어가기로 한다.
먼저 간단한 테스트의 등록 예이다.

#include <boost/test/auto_unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>

BOOST_AUTO_UNIT_TEST (MyFirstTest)
{
float fnum = 2.00001f;
BOOST_CHECK_CLOSE(fnum, 2.f, 1e-3);
}



"Fixture"(테스팅의 기본 문맥을 제공하기 위한 구조를 말하며, 보통 테스팅 전의 'setup'과 테스팅 후의 'teardown' 단계가 존재한다)는 다음과 같은 방식으로 제공된다.

#include <boost/test/auto_unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include "MyTestClass.h"

struct MyFixture
{
MyFixture()
{
someValue = 2.0;
str = "Hello";
}

float someValue;
std::string str;
MyTestClass myObject;
};


BOOST_AUTO_UNIT_TEST (TestCase1)
{
MyFixture f;
BOOST_CHECK_CLOSE (f.someValue, 2.0f, 0.005f);
f.someValue = 13;
}

BOOST_AUTO_UNIT_TEST (TestCase2)
{
MyFixture f;
BOOST_CHECK_EQUAL (f.str, std::string("Hello"));
BOOST_CHECK_CLOSE (f.someValue, 2.0f, 0.005f);

// Boost deals with this OK and reports the problem
//f.myObject.UseBadPointer();
// Same with this
//myObject.DivideByZero();
}

BOOST_AUTO_UNIT_TEST (TestCase3)
{
MyFixture f;
BOOST_CHECK_EQUAL (1, f.myObject.s_currentInstances);
BOOST_CHECK_EQUAL (3, f.myObject.s_instancesCreated);
BOOST_CHECK_EQUAL (1, f.myObject.s_maxSimultaneousInstances);
}



C++의 RAII를 통해 픽스처를 제공하는 모습이 확인된다. 다음은 테스트 스윗의 용례이다.

#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
using boost::unit_test::test_suite;


struct MyTest
{
void TestCase1()
{
float fnum = 2.00001f;
BOOST_CHECK_CLOSE(fnum, 2.f, 1e-3);
}

void TestCase2()
{
}
};

test_suite * GetSuite1()
{
test_suite * suite = BOOST_TEST_SUITE("my_test_suite");

boost::shared_ptr instance( new MyTest() );
suite->add (BOOST_CLASS_TEST_CASE( &MyTest::TestCase1, instance ));
suite->add (BOOST_CLASS_TEST_CASE( &MyTest::TestCase2, instance ));

return suite;
}

 

#include <boost/test/auto_unit_test.hpp>
using boost::unit_test::test_suite;
extern test_suite * GetSuite1();

boost::unit_test::test_suite*
init_unit_test_suite( int /* argc */, char* /* argv */ [] ) {
test_suite * test = BOOST_TEST_SUITE("Master test suite");
test->add( boost::unit_test::ut_detail::auto_unit_test_suite() );
test->add(GetSuite1());
return test;
}



init_unit_test_suite()는 부스트의 단위 테스트 프레임워크 사용 시 main 함수 진입부에서 자동으로 불리워지게 되는 함수이다. 좀 더 자세한 설명 및 TDD에 관한 여러가지 유용한 글들은 여기서 확인하도록 하자.
그 밖에도 부스트의 test 라이브러리에서는 테스팅 환경이 아닌 프러덕션 환경(다시 말해, 테스팅을 위한 실행 파일이 아니라 실제 제품 실행 파일을 만들어내는 환경)에서의 일관된 예외 처리를 위한 방편으로 "Program Execution Monitor"라는 것도 제공하고 있다. 간단히 기존 main 대신 다음과 같이 cpp_main을 사용하면 된다. 이에 대한 자세한 사항은 역시 매뉴얼을 참조하도록 한다.

#include <iostream>

int cpp_main( int, char* [] ) // note name
{
std::cout << "Hello, worldn";

return 0;
}

자료 구조

* any
void*를 대체하는 C++ 클래스라고 보면 된다. void 포인터로 변환하면 변환하는 순간 모든 타입 정보가 유실되지만, any 클래스는 그렇지 않다. 다음 예들을 보면 쉽게 이해가 갈 것이다.

#include <list>
#include <boost/any.hpp>

using boost::any_cast;
typedef std::list<boost::any> many;

void append_int(many & values, int value)
{
boost::any to_append = value;
values.push_back(to_append);
}

void append_string(many & values, const std::string & value)
{
values.push_back(value);
}

void append_char_ptr(many & values, const char * value)
{
values.push_back(value);
}

void append_any(many & values, const boost::any & value)
{
values.push_back(value);
}

void append_nothing(many & values)
{
values.push_back(boost::any());
}

 

bool is_empty(const boost::any & operand)
{
return operand.empty();
}

bool is_int(const boost::any & operand)
{
return operand.type() == typeid(int);
}

bool is_char_ptr(const boost::any & operand)
{
try
{
any_cast<const char *>(operand);
return true;
}
catch(const boost::bad_any_cast &)
{
return false;
}
}

bool is_string(const boost::any & operand)
{
return any_cast<std::string>(&operand);
}

void count_all(many & values, std::ostream & out)
{
out << "#empty == "
<< std::count_if(values.begin(), values.end(), is_empty) << std::endl;
out << "#int == "
<< std::count_if(values.begin(), values.end(), is_int) << std::endl;
out << "#const char * == "
<< std::count_if(values.begin(), values.end(), is_char_ptr) << std::endl;
out << "#string == "
<< std::count_if(values.begin(), values.end(), is_string) << std::endl;
}

 

struct property
{
property();
property(const std::string &, const boost::any &);

std::string name;
boost::any value;
};

typedef std::list<property> properties;



* multi_index
컨테이너 섹션 참조

* pointer container
컨테이너 섹션 참조

* tuple
튜플이란 여러 타입의 개체들을 하나로 묶은 개체라 할 수 있다. 쉽게 임의 타입의 두 개 개체를 묶는 std::pair를 임의의 개수로 확장한 것이라 보면 된다. Python과 같은 일부 언어들에서는 이러한 튜플 타입이 내장으로 지원되기도 한다. 함수에서 여럿의 값을 리턴해야하는 경우 등에 유용할 수 있다. TR1에 채택된 차기 표준 후보이므로 잘 알아두도록 하자. 예제들을 통해 기능들을 하나씩 살펴보자.

tuple<int>
tuple<double&, const double&, const double, double*, const double*>
tuple<A, int(*)(char, int), B(A::*)(C&), C>
tuple<std::string, std::pair<A, B> >
tuple<A*, tuple<const A*, const B&, C>, bool, void*>



위와 같이 템플릿을 통해 다양한 튜플들을 정의할 수 있다.

tuple<int, int, double> add_multiply_divide(int a, int b) {
return make_tuple(a+b, a*b, double(a)/double(b));
}



튜플 개체를 좀 더 간편하게 사용하는 방법은 위와 같이 make_tupe 보조 함수를 사용하는 것이다.
한 튜플 개체의 원소 접근에는 t.get<N>()의 멤버 버전과 get<N>(t)의 비멤버 버전을 사용할 수 있다. 다음 예들을 참고한다.

double d = 2.7; A a;
tuple<int, double&, const A&> t(1, d, a);
const tuple<int, double&, const A&> ct = t;
...
int i = get<0>(t); i = t.get<0>(); // ok
int j = get<0>(ct); // ok
get<0>(t) = 5; // ok
get<0>(ct) = 5; // error, can't assign to const
...
double e = get<1>(t); // ok
get<1>(t) = 3.14; // ok
get<2>(t) = A(); // error, can't assign to const
A aa = get<3>(t); // error: index out of bounds
...
++get<0>(t); // ok, can be used as any variable

 

tuple<std::string, int, A> t1(std::string("same?"), 2, A());
tuple<std::string, long, A> t2(std::string("same?"), 2, A());
tuple<std::string, long, A> t3(std::string("different"), 3, A());

bool operator==(A, A) { std::cout << "All the same to me..."; return true; }

t1 == t2; // true
t1 == t3; // false, does not print "All the..."



위와 같이 관계 연산자를 사용하는 것도 가능하다.
tie 보조 함수를 사용하면 다음과 같이 간단히 튜플의 값들을 개개의 변수들로 언패킹할 수 있다.

int i; char c; double d; 
tie(i, c, d) = make_tuple(1,'a', 5.5);
std::cout << i << " " << c << " " << d;

 

char c;
tie(tuples::ignore, c) = std::make_pair(1, 'a');



위와 같이 ignore 개체를 사용하면, 관심 대상 원소만을 변수로 가져오는 것도 가능하다.
스트리밍 역시 지원되는데, 기본적으로는 다음의 형식을 따른다.

tuple<float, int, std::string> a(1.0f,  2, std::string("Howdy folks!");
// outputs the tuple as: (1.0 2 Howdy folks!)
cout << a;



set_open(char), set_close(char), set_delimiter(char) 로 조작하면 형식을 바꿀 수도 있다.

// outputs the same tuple a as: [1.0,2,Howdy folks!]
cout << tuples::set_open('[') << tuples::set_close(']') << tuples::set_delimiter(',') << a;



입력 스트림도 마찬가지이다. 데이터가 다음과 같이 준비되어 있다고 할 때 코드는 그 아래와 같을 것이다.

(1 2 3) [4:5]

 

tuple<int, int, int> i;
tuple<int, int> j;

cin >> i;
cin >> tuples::set_open('[') >> tuples::set_close(']') >> tules::set_delimiter('=>');
cin >> j;



* variant
컨테이너 섹션 참조


입출력

* format
문자열 및 텍스트 처리 섹션 참조

* program_options
제목 그대로 프로그램 옵션을 처리하기 위한 라이브러리이다. 프로그램 옵션은 커맨드 라인 옵션 혹은 설정 파일 등일 수 있다. 보통의 경우 그냥 하드 코딩 처리하게 되는 경우가 많은데, 그에 비해 여러 장점이 있다고하니 간단히 살펴보자. 이후의 예들에서는 다음의 이름공간 별칭을 사용하고 있다고 가정한다.

namespace po = boost::program_options;



첫 예는 두가지 옵션만을 처리하는 가장 단순한 예이다.

// Declare the supported options.
po::options_description desc("Allowed options");
desc.add_options()
("help", "produce help message")
("compression", po::value<int>(), "set compression level")
;

po::variables_map vm;
po::store(po::parse_command_line(ac, av, desc), vm);
po::notify(vm);

if (vm.count("help")) {
cout << desc << "n";
return 1;
}

if (vm.count("compression")) {
cout << "Compression level was set to "
<< vm["compression"].as<int>() << ".n";
} else {
cout << "Compression level was not set.n";
}



먼저 options_description 클래스를 사용하여 허용 가능한 모든 옵션들을 선언한다. add_options 메쏘드는 특별한 프락시 개체를 리턴하는데 이 개체에서는 () 연산자를 정의하고 있다. 옵션 이름, 값에 관한 정보, 옵션 설명이 매개변수로 입력된다. 위 예의 경우, 첫번째 옵션은 값을 가지지 않으며, 두번째 옵션은 정수형의 값을 가진다.
그 다음, variables_map 클래스의 개체를 하나 선언하고 있다. 그 클래스는 옵션의 값들을 저장하는데 사용되며, 임의 타입 값들을 저장할 수 있다. 다음으로, store, parse_command_line, notify 함수들을 차례로 호출하면, vm에 모든 커맨드 라인 옵션들이 저장된다.
이제는, 옵션들을 원하는대로 사용할 수 있다. variables_map 클래스는 위에서 보이는 것처럼 as 메쏘드 호출로 저장된 값들을 얻어온다는 점을 제외하고는 std::map과 동일한 방식으로 사용 가능하다.
사용 예는 다음과 같을 것이다.

$bin/gcc/debug/first
Compression level was not set.
$bin/gcc/debug/first --help
Allowed options:
--help : produce help message
--compression arg : set compression level
$bin/gcc/debug/first --compression 10
Compression level was set to 10.



물론, 정수값 이외의 다양한 속성들을 옵션값으로 가질 수 있다. 우리가 컴파일러를 하나 작성 중이라고 가정하자. 컴파일러는 최적화 수준, 여럿의 포함 경로, 여럿의 입력 파일들을 취하여 작업을 수행할 것이다. 이제 옵션을 묘사해보자.

int opt;
po::options_description desc("Allowed options");
desc.add_options()
("help", "produce help message")
("optimization", po::value<int>(&opt)->default_value(10),
"optimization level")
("include-path,I", po::value< vector<string> >(),
"include path")
("input-file", po::value< vector<string> >(), "input file")
;



"--help" 옵션은 이미 익숙하다. "optimization" 옵션은 몇가지 새로운 기능들을 선보이고 있다. 먼저, 변수 주소(&opt)를 지정하고 있다. 값들이 저장된 후에는, 그 변수에 옵션 값이 들어가 있을 것이다. 다음으로, 초기값 10을 지정하고 있는데, 이는 사용자가 특정 값을 지정하지 않을 경우 사용하게 될 값이다.
"include-path" 옵션은 커맨드 라인의 경우에서만 유용한 기능을 한가지 보여주고 있다. 바로 짧은 옵션 명이 그것이다. 즉, "--include-path"와 "-I"가 모두 가능하다.

"input-file" 옵션으로 컴파일할 파일명들을 지정할 수 있는데, 이는 그리 자연스럽지 못하다.

compiler --input-file=a.cpp



단순히 다음과 같이 할 수 있다면 더 좋을 것이다.

compiler a.cpp



위와 같은 옵션은 이름이 없는 "위치 기반 옵션"이라 할 수 있는데, 이 역시 처리가능하다. 다음과 같이 해주면된다.

po::positional_options_description p;
p.add("input-file", -1);

po::variables_map vm;
po::store(po::command_line_parser(ac, av).
options(desc).positional(p).run(), vm);
po::notify(vm);



첫 두 줄은 모든 위치 기반 옵션들은 "input-file" 옵션으로 번역되어 처리되어야 함을 말해준다. 또 한가지 주목할 점은 parse_command_line 함수 대신에 command_line_parser 클래스를 사용하고 있다는 것이다. 간단한 경우는 편의상의 랩퍼 함수인 전자로 해결되지만, 위와 같이 추가 설정 필요한 경우에는 후자를 사용해야 한다.
이제 모든 옵션을 지정하고 파싱하였으니, 적절히 옵션에 따라 작업을 해주기만 하면 된다.

if (vm.count("include-path"))
{
cout << "Include paths are: "
<< vm["include-path"].as< vector<string> >() << "n";
}

if (vm.count("input-file"))
{
cout << "Input files are: "
<< vm["input-file"].as< vector<string> >() << "n";
}

cout << "Optimization level is " << opt << "n";



다음은 사용 예이다.

$bin/gcc/debug/options_description --help
Usage: options_description [options]
Allowed options:
--help : produce help message
--optimization arg : optimization level
-I [ --include-path ] arg : include path
--input-file arg : input file
$bin/gcc/debug/options_description
Optimization level is 10
$bin/gcc/debug/options_description --optimization 4 -I foo a.cpp
Include paths are: foo
Input files are: a.cpp
Optimization level is 4



마지막으로 옵션을 그룹별로 묶어, 커맨드 라인에서만 지정가능한 옵션과 양쪽 모두에서 지정가능한 옵션, 그리고 도움말이 표시되지 않아야 하는 숨겨진 옵션(위 예의 "--input-file" 같은)들을 따로 처리하는 고급 용례가 다음에 나와 있다. 여기서는 커맨드 라인과 설정 파일에서 같은 옵션이 지정될 경우 어떻게 처리해야 하는가 하는 문제도 다루고 있는데 자세한 설명은 매뉴얼을 참조한다.

// Declare a group of options that will be 
// allowed only on command line
po::options_description generic("Generic options");
generic.add_options()
("version,v", "print version string")
("help", "produce help message")
;

// Declare a group of options that will be
// allowed both on command line and in
// config file
po::options_description config("Configuration");
config.add_options()
("optimization", po::value<int>(&opt)->default_value(10),
"optimization level")
("include-path,I",
po::value< vector<string> >()->composing(),
"include path")
;

// Hidden options, will be allowed both on command line and
// in config file, but will not be shown to the user.
po::options_description hidden("Hidden options");
hidden.add_options()
("input-file", po::value< vector<string> >(), "input file")
;

po::options_description cmdline_options;
cmdline_options.add(generic).add(config).add(hidden);

po::options_description config_file_options;
config_file_options.add(config).add(hidden);

po::options_description visible("Allowed options");
visible.add(generic).add(config);

 

$bin/gcc/debug/multiple_sources
Include paths are: /opt
Optimization level is 1
$bin/gcc/debug/multiple_sources --help
Allows options:

Generic options:
-v [ --version ] : print version string
--help : produce help message

Configuration:
--optimization n : optimization level
-I [ --include-path ] path : include path

$bin/gcc/debug/multiple_sources --optimization=4 -I foo a.cpp b.cpp
Include paths are: foo /opt
Input files are: a.cpp b.cpp
Optimization level is 4



* serialization
매우 훌륭한 연재(serialization) 라이브러리이다. 또한 별도의 "아카이브(archive)" 개념으로 유연성을 제공한다. 여기서 아카이브는 연재 데이터의 특정 포맷 렌더링을 나타낸다. 본 라이브러리는 다음과 같은 목표들을 염두에 두고 만들어졌다.
* 코드 호환성 - ANSI C++ 기능에만 의존한다.
* 코드 경제성 - RTTI, 템플릿, 다중 상속 등과 같은 C++의 기능을 적절한 곳에 활용하여 코드를 더 짧고 사용하기 쉽게 만든다.
* 각 클래스 정의별로 독립적인 버전을 관리한다. 즉, 한 클래스 정의가 바뀌어도, 여전히 예전 파일들을 새 버전의 클래스로 불러올 수 있다.
* 깊은 포인터 저장 및 보구. 즉, 포인터를 저장 및 불러오기 시, 그 포인터가 가리키는 데이터가 적절히 저장 및 불러오기 된다.
* 공유 데이터에 대한 포인터의 불러오기가 적절히 이루어진다.
* STL 컨테이너 및 기타 자주 쓰이는 템플릿들의 연재.
* 데이터 호환성 - 한 플랫폼에서 생성된 바이트 스트림을 다른 플랫폼에서 아무 문제없이 불러들일 수 있어야 한다.
* 클래스 연재와 아카이브 포맷의 상호 독립적인 스펙 지정. 즉, 클래스의 연재방식을 변경하지 않고도 임의의 C++ 자료 구조 집합을 임의의 파일 포맷으로 연재할 수 있다.
* 무간섭(Non-intrusive). 변경 불가능한 클래스에도 연재를 적용할 수 있다. 즉, 클래스를 연재하기 위해서 특정 기반 클래스로부터 상속받아야할 필요가 없다. 변경할 수 없거나 변경하기 싫은 클래스 라이브러리 내의 클래스들을 쉽게 연재할 수 있으려면, 이러한 무간섭성이 필수적이다.
* 새로운 유형의 아카이브를 쉽게 생성할 수 있도록 아카이브 인터페이스는 충분히 단순해야 한다.
* 연재 데이터를 XML로 표현하는 아카이브를 유용한 방식으로 생성할 수 있을 정도로 풍부한 아카이브 인터페이스를 제공해야 한다.
간단한 예를 통해 차근차근 기능을 살펴보자. 먼저 출력 아카이브는 출력 데이터스트림과 유사하며 다음과 같이 << 또는 & 연산자를 사용하여 데이터를 저장할 수 있다.

ar << data;
ar & data;



입력 아카이브는 입력 데이터스트림과 유사하며, 마찬가지로 >> 또는 & 연산자를 사용하여 데이터를 불러올 수 있다.

ar >> data;
ar & data;



가장 간단한 예부터 시작한다.

#include <fstream>

// include headers that implement a archive in simple text format
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

/////////////////////////////////////////////////////////////
// gps coordinate
//
// illustrates serialization for a simple type
//
class gps_position
{
private:
friend class boost::serialization::access;
// When the class Archive corresponds to an output archive, the
// & operator is defined similar to <<. Likewise, when the class Archive
// is a type of input archive the & operator is defined similar to >>.
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & degrees;
ar & minutes;
ar & seconds;
}
int degrees;
int minutes;
float seconds;
public:
gps_position(){};
gps_position(int d, int m, float s) :
degrees(d), minutes(m), seconds(s)
{}
};

int main() {
// create and open a character archive for output
std::ofstream ofs("filename");

// create class instance
const gps_position g(35, 59, 24.567f);

// save data to archive
{
boost::archive::text_oarchive oa(ofs);
// write class instance to archive
oa << g;
// archive and stream closed when destructors are called
}

// ... some time later restore the class instance to its orginal state
gps_position newg;
{
// create and open an archive for input
std::ifstream ifs("filename", std::ios::binary);
boost::archive::text_iarchive ia(ifs);
// read class state from archive
ia >> newg;
// archive and stream closed when destructors are called
}
return 0;
}



serialize 템플릿 멤버 함수가 연재를 통해 저장 및 불러오기 모두를 담당하고 있다. 다음과 같은 무간섭 버전도 가능한다.

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

class gps_position
{
public:
int degrees;
int minutes;
float seconds;
gps_position(){};
gps_position(int d, int m, float s) :
degrees(d), minutes(m), seconds(s)
{}
};

namespace boost {
namespace serialization {

template<class Archive>
void serialize(Archive & ar, gps_position & g, const unsigned int version)
{
ar & g.degrees;
ar & g.minutes;
ar & g.seconds;
}

} // namespace serialization
} // namespace boost



물론, 이러한 무간섭 버전이 가능하려면, 해당 클래스가 연재에 충분한 정보를 공용 인터페이스로 노출시키고 있어야 한다. 다음은 연재가능한 멤버를 가지는 연재 클래스의 예이다.

class bus_stop
{
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & latitude;
ar & longitude;
}
gps_position latitude;
gps_position longitude;
protected:
bus_stop(const gps_position & lat_, const gps_position & long_) :
latitude(lat_), longitude(long_)
{}
public:
bus_stop(){}
// See item # 14 in Effective C++ by Scott Meyers.
// re non-virtual destructors in base classes.
virtual ~bus_stop(){}
};



다음은 파생 클래스에서 어떻게 연재를 해야하는지 보여준다.

#include <boost/serialization/base_object.hpp>

class bus_stop_corner : public bus_stop
{
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
// serialize base class information
ar & boost::serialization::base_object<bus_stop>(*this);
ar & street1;
ar & street2;
}
std::string street1;
std::string street2;
virtual std::string description() const
{
return street1 + " and " + street2;
}
public:
bus_stop_corner(){}
bus_stop_corner(const gps_position & lat_, const gps_position & long_,
const std::string & s1_, const std::string & s2_
) :
bus_stop(lat_, long_), street1(s1_), street2(s2_)
{}
};



파생 클래스에서 기반 클래스를 연재하고 있음에 주목한다. 기반 클래스 serialize 함수를 직접 호출해서는 안된다. 중복을 제거하기 위해 이미 저장소에 쓰여진 인스턴스들을 추적한다거나 아카이브에 클래스 버전 정보를 쓰는 등의 추가 작업이 필요하기 때문이다. 이러한 이유로, serialize 함수는 비공개(private)로 설정하는 것이 좋다.
포인터의 경우도 다음과 같이 간단히 해결된다. 다음 예에서 우리는 버스 정류소의 배열로 버스 경로를 정의하는데, 버스 정류소의 유형이 다양할 수 있으며(bus_stop이 기반 클래스였음을 기억하자), 하나의 버스 정류소가 여러 경로에 등장할 수 있다는 점에 주의해야 한다.

class bus_route
{
friend class boost::serialization::access;
bus_stop * stops[10];
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
int i;
for(i = 0; i < 10; ++i)
ar & stops[i];
}
public:
bus_route(){}
};



간단히 배열의 각 원소를 연재하고 있는데, 주의할 것은 각 원소가 포인터라는 점이다. 당연히 포인터 주소값을 적거나 읽어서는 의미가 없으며, 여러 포인터가 가리키고 있는 데이터의 중복 연재도 조심해야 한다. 또한, bus_stop이라는 기반 클래스의 포인터로 일괄 표현되고 있지만 실질적으로는 각 버스 정류소의 유형이 서로 다를 수 있으므로, 그 다형성도 고려해주어야 한다. 부스트 연재 라이브러리는 이러한 모든 사항은 고려하여 알아서 연재해준다(단, 경우에 따라서 각 파생 클래스들을 명시적으로 '등록'해주어야 하는 경우도 있는데, 이에 대해서는 매뉴얼을 참조하도록 한다).
또한 배열 자체도 자동 인식하여 지원하기 때문에, 위보다 더 간단하게 하는 것도 가능하다.

class bus_route
{
friend class boost::serialization::access;
bus_stop * stops[10];
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & stops;
}
public:
bus_route(){}
};



부스트 연재 라이브러리는 STL 컨테이너들도 지원한다. 따라서 위의 예는 아래와 같이 바꿀 수 있다.

#include <boost/serialization/list.hpp>

class bus_route
{
friend class boost::serialization::access;
std::list<bus_stop *> stops;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & stops;
}
public:
bus_route(){}
};



이미 위와 같은 형태 bus_route 클래스를 배포하였는데, 나중에 경로에 버스 기사의 이름을 추가할 필요가 생길 수 있다. 이 때, 버전 지정 기능이 유용하게 쓰인다. 다음과 같이 해주면 이전 배포된 포맷으로 저장된 파일도 문제없이 불러올 수 있다(특별히 버전이 지정되지 않을 경우, 0 이 기본값이다).

#include <boost/serialization/list.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/version.hpp>

class bus_route
{
friend class boost::serialization::access;
std::list<bus_stop *> stops;
std::string driver_name;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
// only save/load driver_name for newer archives
if(version > 0)
ar & driver_name;
ar & stops;
}
public:
bus_route(){}
};

BOOST_CLASS_VERSION(bus_route, 1)



이렇게 클래스 별로 버전 관리가 이루어지기 때문에, 별도의 파일별 버전이 필요치 않다. 이제까지는 serialize 함수를 통해 저장 및 불러오기를 한번에 처리하고 있었다. 하지만 경우에 따라서는 저장/불러오기가 다른 로직을 가져야할 수 있다. 가령, 위의 클래스는 다음과 같이 고쳐야 좀 더 올바른 버전 관리가 된다고 할 수 있을 것이다.

#include <boost/serialization/list.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/version.hpp>
#include <boost/serialization/split_member.hpp>

class bus_route
{
friend class boost::serialization::access;
std::list<bus_stop *> stops;
std::string driver_name;
template<class Archive>
void save(Archive & ar, const unsigned int version) const
{
// note, version is always the latest when saving
ar & driver_name;
ar & stops;
}
template<class Archive>
void load(Archive & ar, const unsigned int version)
{
if(version > 0)
ar & driver_name;
ar & stops;
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
public:
bus_route(){}
};

BOOST_CLASS_VERSION(bus_route, 1)



BOOST_SERIALIZATION_SPLIT_MEMBER() 매크로를 사용하면 아카이브의 유형에 따라 적절하게 save 또는 load 호출을 하는 코드가 자동생성된다.

지금까지는 연재 기능에 초점을 두어 설명하였다. 실제 데이터의 렌더링은 하지만 아키이브 클래스를 통해 이루어진다. 본 예에서는 text_oarchive/text_iarchive를 각각 저장 및 불러오기에 사용하였다. 이렇게 플랫폼 독립적인 텍스트로 연재하는 아카이브 외에도 고유 이진 데이터 및 xml 포맷 데이터로 렌더링해주는 아카이브 클래스가 기본 제공된다. 또한 필요에 따라 기존 클래스에서의 상속을 통해서 아니면 완전 새로이 별도의 아카이브 클래스를 구현하는 것도 가능하다.

부스트 연재 라이브러리를 게임 내의 연재(serialization) 솔루션으로 강력히 추천하는 바이다.


언어간 지원

* python
C++과 파이썬과의 연결 고리 역할을 해주는 라이브러리로, 문명4탄에서도 이 부스트.파이썬을 이용해 다양한 기능을 구현하고 게임 모딩의 가능성을 넓히고 있다고 한다. C++ 클래스 함수 및 개체들을 파이썬으로 내보내거나 혹은 그 반대를 무간섭적으로(기존 코드의 수정없이) 행할 수 있게 해준다. 먼저 이 섹션을 이해하려면 파이썬에 대해 기초적인 지식이 필요함을 미리 말해둔다.
가장 간단한 예부터 시작해보자.

char const* greet()
{
return "hello, world";
}



위 함수는 다음과 같이 Boost.Python 랩퍼를 작성하여 파이썬으로 내보낼 수 있다.

#include <boost/python.hpp>
using namespace boost::python;

BOOST_PYTHON_MODULE(hello)
{
def("greet", greet);
}



이것이 전부다. 이제 이를 공유 라이브러리로 빌드하고, 결과 DLL을 파이썬이 찾을 수 있도록 한다. 다음은 파이썬 세션의 예이다.

>>> import hello
>>> print hello.greet()
hello, world



이를 위한 빌드 과정은 매뉴얼에 자세히 나와 있다. 다음은 클래스를 내보내는 예이다.

struct World
{
void set(std::string msg) { this->msg = msg; }
std::string greet() { return msg; }
std::string msg;
};



위는 다음과 같이 랩퍼를 작성하여 내보낼 수 있다.

#include <boost/python.hpp>
using namespace boost::python;

BOOST_PYTHON_MODULE(hello)
{
class_<World>("World")
.def("greet", &World::greet)
.def("set", &World::set)
;
}



공유 라이브러리로 빌드 후, 파이썬에서의 사용 예는 다음과 같다.

>>> import hello
>>> planet = hello.World()
>>> planet.set('howdy')
>>> planet.greet()
'howdy'



다음은 생성자가 별도로 존재하는 경우를 살펴보자.

struct World
{
World(std::string msg): msg(msg) {} // added constructor
void set(std::string msg) { this->msg = msg; }
std::string greet() { return msg; }
std::string msg;
};



랩핑 코드는 다음과 같이 바뀌어야 한다.

#include <boost/python.hpp>
using namespace boost::python;

BOOST_PYTHON_MODULE(hello)
{
class_<World>("World", init<std::string>())
.def("greet", &World::greet)
.def("set", &World::set)
;
}



init<std::string>()는 std::string 하나를 인자로 취하는 생성자를 내보낸다(파이썬에서는, "_init_"이 생성자이다). 한편, 아무런 생성자도 내보내지 않고자하는 경우에는, no_init을 대신 사용하면 된다.

class_<Abstract>("Abstract", no_init)



이 코드는 실질적으로 항상 파이썬 RuntimeError 예외를 발생시키는 init 메쏘드를 추가한다.
이제 클래스 데이터 멤버를 내보내는 방법을 알아보자. 각 데이터 멤버를 read-only 또는 read-write로 내보낼 수 있다.

struct Var
{
Var(std::string name) : name(name), value() {}
std::string const name;
float value;
};



위의 클래스 및 그 데이터 멤버들은 다음과 같이 파이썬으로 내보낼 수 있다.

class_<Var>("Var", init<std::string>())
.def_readonly("name", &Var::name)
.def_readwrite("value", &Var::value);


파이썬에서는, Var 클래스를 hello 이름공간 내에 넣었다고 할 때, 다음과 같이 접근할 수 있다.

>>> x = hello.Var('pi')
>>> x.value = 3.14
>>> print x.name, 'is around', x.value
pi is around 3.14



value는 읽기쓰기가 가능하지만 name은 읽기전용임에 주의한다.

>>> x.name = 'e' # can't change name
Traceback (most recent call last):
File "<stdin>", line 1, in#
AttributeError: can#t set attribute



C++에서는, 데이터 멤버를 공용으로 만들기보다는 접근 함수를 제공하여 캡슐화 할 것은 권장한다. 접근 함수가 클래스의 속성(property)을 외부로 노출시킨다고 할 수 있다.

struct Num
{
Num();
float get() const;
void set(float value);
...
};



그러나, 파이썬에서는 속성을 언어 차원에서 지원하기 때문에, 속성의 직접 노출을 걱정할 필요가 없다. 속성 접근이 내부적으로는 함수 호출로 이루어지도록 제어할 수 있기 때문이다. 따라서 다음과 같이 Num 클래스를 내보낼 수 있다.

class_<Num>("Num")
.add_property("rovalue", &Num::get)
.add_property("value", &Num::get, &Num::set);



파이썬에서 다음과 같을 것이다.

>>> x = Num()
>>> x.value = 3.14
>>> x.value, x.rovalue
(3.14, 3.14)
>>> x.rovalue = 2.17 # error!



다형적 클래스, 즉 상속 관계가 존재하는 클래스의 경우는 어떻게 될까? 다음에 간단한 상속의 예가 나와 있다.

struct Base { virtual ~Base(); };
struct Derived : Base {};

void b(Base*);
void d(Derived*);
Base* factory() { return new Derived; }



Base 클래스는 기존처럼 등록해주면 된다.

class_<Base>("Base")
/*...*/
;



Derived 클래스의 경우에는, 다음과 같은 방식으로 상속 관계를 Boost.Python에 알려줄 수 있다.

class_<Derived, bases<Base> >("Derived")
/*...*/
;



이렇게 하면 Derived가 Base의 파이썬 메쏘드들을 자동으로 상속받게 되고, 파이썬에서도 두 클래스 계층 구조의 다형성이 보장된다.
이제, C++ 함수 b, d, factory를 내보낼 수 있다.

def("b", b);
def("d", d);
def("factory", factory);



factory 함수는 Derived 클래스의 새 인스턴스를 생성하는데 사용된다. 그러한 경우에는 return_value_policy<manage_new_object> 를 사용하여 파이썬이 Base에 대한 포인터를 가지고 해당 파이썬 개체가 소멸될 때까지 그 인스턴스를 담고 있도록 할 수 있다. 이러한 Boost.Python의 여러 호출 정책에 대해서는 매뉴얼을 참고하도록 한다.

// Tell Python to take ownership of factory's result
def("factory", factory,
return_value_policy<manage_new_object>());



이 밖에도 가상 함수, 연산자 등을 내보내는 방법들이 존재하는데, 이에 관해서는 역시 매뉴얼을 참조하자.


메모리

* pool
메모리 풀 라이브러리. 노파심에 다시 한번 정리하면. 풀이란 단일 크기 단위로 메모리를 할당/해제해야 한다는 제약을 수용함으로써 메모리 할당/해제의 효율을 극대화하고자 하는 시도이다. 부스트 풀 라이브러리는 사용자의 요구 사항에 맞는 여러 인터페이스들을 제공하고 있다. 풀이 한 개체로서 사용되는 경우와 싱글톤으로 사용되는 경우가 있다. 그리고 메모리 부족시 Null을 리턴하는 경우와 예외를 던지는 경우가 있다. 차례대로 살펴보자.
* pool
개체로서 사용되며, Null을 리턴하는 경우이다.

void func()
{
boost::pool<> p(sizeof(int));
for (int i = 0; i < 10000; ++i)
{
int * const t = p.malloc();
... // Do something with t; don't take the time to free() it
}
} // on function exit, p is destroyed, and all malloc()'ed ints are implicitly freed


* object_pool
위 pool의 경우와 같지만, 한 덩어리로 할당하는 개체의 타입 정보를 인식하고 있다. 따라서, 소멸 시 적절한 소멸자가 호출될 수 있다.

struct X { ... }; // has destructor with side-effects

void func()
{
boost::object_pool<X> p;
for (int i = 0; i < 10000; ++i)
{
X * const t = p.malloc();
... // Do something with t; don't take the time to free() it
}
} // on function exit, p is destroyed, and all destructors for the X objects are called



* singleton_pool
싱글톤으로 사용되며, Null을 리턴하는 경우이다. 싱글톤으로 사용된다는 것을 제외하고는 pool과 같다.

struct MyPoolTag { };

typedef boost::singleton_pool<MyPoolTag, sizeof(int)> my_pool;
void func()
{
for (int i = 0; i < 10000; ++i)
{
int * const t = my_pool::malloc();
... // Do something with t; don't take the time to free() it
}
// Explicitly free all malloc()'ed int's
my_pool::purge_memory();
}



* pool_alloc
예외를 발생시키며, 싱글톤으로 사용된다. 사실 이 pool_alloc은 singleton_pool 인터페이스를 기반으로 한다. 또한, 표준 할당자 호환 클래스이기 때문에, 표준 컨테이너 등에서 할당자로 사용 가능하다.

void func()
{
std::vector<int, boost::pool_allocator<int> > v;
for (int i = 0; i < 10000; ++i)
v.push_back(13);
} // Exiting the function does NOT free the system memory allocated by the pool allocator
// You must call
// boost::singleton_pool<boost::pool_allocator_tag, sizeof(int)>::release_memory()
// in order to force that



게임 내에서는 풀 메모리 관리가 필요한 경우가 많으므로, 상당히 요긴하게 쓰일 수 있는 라이브러리라 하겠다.

* smart_ptr
가비지 컬렉션이 지원되지 않은 C++에서 메모리 자원 관리에 필수적이라 할 수 있는 스마트 포인터 라이브러리이다. 다음과 같은 스마트 포인터 클래스들을 지원한다.

  • scoped_ptr : <boost/scoped_ptr.hpp> - 단일 개체를 고유하게 소유. 복사불가능.
  • scoped_array : <boost/scoped_array.hpp> - 배열을 고유하게 소유. 복사불가능
  • shared_ptr : <boost/shared_ptr.hpp> - 여러 포인터들이 한 개체에 대한 소유권 공유.
  • shared_array : <boost/shared_array.hpp> - 여러 포인터들이 한 배열에 대한 소유권 공유.
  • weak_ptr : <boost/weak_ptr.hpp> - shared_ptr가 소유하고 있는 한 개체에 대한 소유권 없는 관찰자의 역할.
  • intrusive_ptr : <boost/intrusive_ptr.hpp> - 참조 카운트를 클래스에 내재하는 방식으로 작동하는 공유 포인터.


shared_ptr, weak_ptr 등은 TR1으로 확정된 차기 표준 후보이므로 널리 사용할 것을 권장한다. 몇몇 포인터 유형별로 사용 예를 살펴보고 넘어가도록 하자.

  • scoped_ptr
#include <boost/scoped_ptr.hpp>
#include <iostream>

struct Shoe { ~Shoe() { std::cout << "Buckle my shoen"; } };

class MyClass {
boost::scoped_ptr<int> ptr;
public:
MyClass() : ptr(new int) { *ptr = 0; }
int add_one() { return ++*ptr; }
};

int main()
{
boost::scoped_ptr<Shoe> x(new Shoe);
MyClass my_instance;
std::cout << my_instance.add_one() << 'n';
std::cout << my_instance.add_one() << 'n';
}



출력 결과는 다음과 같다.

1
2
Buckle my shoe
  • shared_ptr
#include <vector>
#include <set>
#include <iostream>
#include <algorithm>
#include <boost/shared_ptr.hpp>

// The application will produce a series of
// objects of type Foo which later must be
// accessed both by occurrence (std::vector)
// and by ordering relationship (std::set).

struct Foo
{
Foo( int _x ) : x(_x) {}
~Foo() { std::cout << "Destructing a Foo with x=" << x << "n"; }
int x;
/* ... */
};

typedef boost::shared_ptr<Foo> FooPtr;

struct FooPtrOps
{
bool operator()( const FooPtr & a, const FooPtr & b )
{ return a->x > b->x; }
void operator()( const FooPtr & a )
{ std::cout << a->x << "n"; }
};

int main()
{
std::vector<FooPtr> foo_vector;
std::set<FooPtr,FooPtrOps> foo_set; // NOT multiset!

FooPtr foo_ptr( new Foo( 2 ) );
foo_vector.push_back( foo_ptr );
foo_set.insert( foo_ptr );

foo_ptr.reset( new Foo( 1 ) );
foo_vector.push_back( foo_ptr );
foo_set.insert( foo_ptr );

foo_ptr.reset( new Foo( 3 ) );
foo_vector.push_back( foo_ptr );
foo_set.insert( foo_ptr );

foo_ptr.reset ( new Foo( 2 ) );
foo_vector.push_back( foo_ptr );
foo_set.insert( foo_ptr );

std::cout << "foo_vector:n";
std::for_each( foo_vector.begin(), foo_vector.end(), FooPtrOps() );

std::cout << "nfoo_set:n";
std::for_each( foo_set.begin(), foo_set.end(), FooPtrOps() );
std::cout << "n";

// Expected output:
//
// foo_vector:
// 2
// 1
// 3
// 2
//
// foo_set:
// 3
// 2
// 1
//
// Destructing a Foo with x=2
// Destructing a Foo with x=1
// Destructing a Foo with x=3
// Destructing a Foo with x=2

return 0;
}



기타 자세한 사항은 매뉴얼 및 CUJ 2005년 9, 10, 11월호를 참조하도록 한다.

* utility
알고리즘 섹션 참조


파싱


프로그래밍 인터페이스

* function
함수 개체 및 고차원 프로그래밍 섹션 참조


기타

* boost/cast
아래와 같은 두가지 캐스트 함수 템플릿을 제공한다.

namespace boost {

template <class Derived, class Base>
inline Derived polymorphic_cast(Base* x);
// Throws: std::bad_cast if ( dynamic_cast<Derived>(x) == 0 )
// Returns: dynamic_cast<Derived>(x)

template <class Derived, class Base>
inline Derived polymorphic_downcast(Base* x);
// Effects: assert( dynamic_cast<Derived>(x) == x );
// Returns: static_cast<Derived>(x)

}



주석에서 설명하고 있는 바와 같이, polymorphic_cast는 dynamic_cast와 유사하나 포인터 시에도 캐스트 실패 시 0을 리턴하는 대신 예외를 던진다. polymorphic_downcast는 디버그 버전에서만 dynamic_cast로 체크를 하고 릴리즈 버전에서는 그냥 빠른 static_cast로 도는 버전이다. '''polymorphic_downcast는 요긴하게 쓰일 수 있을 듯하다.'''


* numeric/conversion
수학 및 수치 관련 섹션 참조

* crc
Cyclic Redundancy Code 라이브러리. 쓰일 곳이 있을지는 확실히 모르겠으나... 필요할 수도 있을 듯하여... 다음 예제로 설명을 대신한다.

#include <boost/crc.hpp>  // for boost::crc_32_type

#include <cstdlib> // for EXIT_SUCCESS, EXIT_FAILURE
#include <exception> // for std::exception
#include <fstream> // for std::ifstream
#include <ios> // for std::ios_base, etc.
#include <iostream> // for std::cerr, std::cout
#include <ostream> // for std::endl

// Redefine this to change to processing buffer size
#ifndef PRIVATE_BUFFER_SIZE
#define PRIVATE_BUFFER_SIZE 1024
#endif

// Global objects
std::streamsize const buffer_size = PRIVATE_BUFFER_SIZE;

// Main program
int
main
(
int argc,
char const * argv[]
)
try
{
boost::crc_32_type result;

for ( int i = 1 ; i < argc ; ++i )
{
std::ifstream ifs( argv[i], std::ios_base::binary );

if ( ifs )
{
do
{
char buffer[ buffer_size ];

ifs.read( buffer, buffer_size );
result.process_bytes( buffer, ifs.gcount() );
} while ( ifs );
}
else
{
std::cerr << "Failed to open file '" << argv[i] << "'."
<< std::endl;
}
}

std::cout << std::hex << std::uppercase << result.checksum() << std::endl;
return EXIT_SUCCESS;
}
catch ( std::exception &e )
{
std::cerr << "Found an exception with '" << e.what() << "'." << std::endl;
return EXIT_FAILURE;
}
catch ( ... )
{
std::cerr << "Found an unknown exception." << std::endl;
return EXIT_FAILURE;
}



* filesystem
부스트 파일시스템 라이브러리는 경로명, 파일 및 디렉토리들을 질의하고 조작할 수 있는 플랫폼 독립적 기능들을 제공한다. 본 라이브러리는 여러 헤더로 이루어지는데, 모두 boost/filesystem 디렉토리 안에 있다.
* path.hpp 헤더는 C++ 프로그램에서 경로명을 호환성 있게 표현할 수 있게 해준다. 유효성 검사 함수 또한 제공된다.
* operations.hpp 헤더는 파일 및 디렉토리를 대상으로 동작하는 함수들을 제공하며, directory_iterator 클래스를 포함하고 있다.
* fstream.hpp 헤더는 C++ 표준 라이브러리의 fstream 헤더와 유사한 컴포넌트를 제공하는데, char * 대신 path 개체를 통해 파일을 지정한다는 점만 다르다.
* exception.hpp 헤더는 filesystem_error 클래스를 제공한다.
* convenience.hpp 헤더는 저수준 함수들을 유용한 방식으로 결합한 편의 함수들을 제공한다.

간단한 예를 통해 기능을 살펴보자.

#include "boost/filesystem/operations.hpp" // includes boost/filesystem/path.hpp
#include "boost/filesystem/fstream.hpp" // ditto
#include <iostream> // for std::cout
using boost::filesystem; // for ease of tutorial presentation;
// a namespace alias is preferred in real code



path 클래스 개체는 다음과 같이 생성할 수 있다.

path my_path( "some_dir/file.txt" );



이 때 path 생성자에 건내어주는 문자열은 일반 호환 경로 포맷으로 되어이었야 한다(일반 호환 경로 포맷에 관한 자세한 사항은 레퍼런스를 참고한다). my_path 내용을 운영 체제 의존적인 형태로 넘겨주는 접근 함수들이 제공된다.
path 클래스는 const char * 와 const std::string & 을 받는 생성자를 제공하기 때문에, 다음 코드의 파일시스템 라이브러리 함수들이 const path & 인자들을 받도록 되어있더라도, 사용자는 그냥 C-스타일 문자열을 건낼 수 있다.

remove_all( "foobar" );
create_directory( "foobar" );
ofstream file( "foobar/cheeze" );
file << "tastes good!n";
file.close();
if ( !exists( "foobar/cheeze" ) )
std::cout << "Something is rotten in foobarn";


운영 체제 의존적 포맷을 위한 path 클래스 생성자들도 제공되어, 다음과 같은 상황에 유용하게 쓰일 수 있다.

int main( int argc, char * argv[] ) {
path arg_path( argv[1], native ); // native means use O/S path format



path 개체를 표현식 내에서 쉽게 사용할 수 있도록 하기 위해, 경로를 덧붙이는 역할을 하는 / 연산자가 제공된다.

ifstream file1( arg_path / "foo/bar" );
ifstream file2( arg_path / "foo" / "bar" );



".." 표기를 통해 부모 디렉토리를 참조할 수 있다.
directory_iterator 클래스는 본 라이브러리의 중용한 구성 요소 중 하나이다. 한 디렉토리의 내용물에 대한 입력 반복자를 제공하는데, 이 때, path 클래스가 값 타입이 된다.
다음 함수는, 디렉토리 경로와 파일명이 주어지면, 해당 디렉토리 및 그 하위 디렉토리를 재귀적으로 검색하면서 그 파일명의 파일을 찾는다. 찾았는지 여부를 나타내는 bool 값을 리턴하며, 찾았을 경우, 찾은 파일에 대한 경로를 변수로 출력한다.

bool find_file( const path & dir_path,     // in this directory,
const std::string & file_name, // search for this name,
path & path_found ) // placing path here if found
{
if ( !exists( dir_path ) ) return false;
directory_iterator end_itr; // default construction yields past-the-end
for ( directory_iterator itr( dir_path );
itr != end_itr;
++itr )
{
if ( is_directory( *itr ) )
{
if ( find_file( *itr, file_name, path_found ) ) return true;
}
else if ( itr->leaf() == file_name ) // see below
{
path_found = *itr;
return true;
}
}
return false;
}



표현식 itr->leaf() == file_name 에서는 반복자가 리턴하는 path 개체에 대해 leaf() 함수를 호출하고 있다. leaf() 멤버 함수는 path 개체의 마지막 파일 또는 디렉토리 이름의 사본 문자열을 리턴한다. 이 leaf() 함수 외에 제공되는 여러 함수들도 tree/root/branch/leaf의 은유에 기반한 이름을 사용한다.
마지막으로 간단한 ls 명령어 구현 예제를 싣는다. 기타 자세한 사항은 매뉴얼을 참조하자.

#include "boost/filesystem/operations.hpp"
#include "boost/filesystem/path.hpp"
#include <iostream>

namespace fs = boost::filesystem;

int main( int argc, char* argv[] )
{
fs::path full_path( fs::initial_path() );

if ( argc > 1 )
full_path = fs::system_complete( fs::path( argv[1], fs::native ) );
else
std::cout << "nusage: simple_ls [path]" << std::endl;

unsigned long file_count = 0;
unsigned long dir_count = 0;
unsigned long err_count = 0;

if ( !fs::exists( full_path ) )
{
std::cout << "nNot found: " << full_path.native_file_string() << std::endl;
return 1;
}

if ( fs::is_directory( full_path ) )
{
std::cout << "nIn directory: "
<< full_path.native_directory_string() << "nn";
fs::directory_iterator end_iter;
for ( fs::directory_iterator dir_itr( full_path );
dir_itr != end_iter;
++dir_itr )
{
try
{
if ( fs::is_directory( *dir_itr ) )
{
++dir_count;
std::cout << dir_itr->leaf()<< " [directory]n";
}
else
{
++file_count;
std::cout << dir_itr->leaf() << "n";
}
}
catch ( const std::exception & ex )
{
++err_count;
std::cout << dir_itr->leaf() << " " << ex.what() << std::endl;
}
}
std::cout << "n" << file_count << " filesn"
<< dir_count << " directoriesn"
<< err_count << " errorsn";
}
else // must be a file
{
std::cout << "nFound: " << full_path.native_file_string() << "n";
}
return 0;
}



* optional
값을 리턴해야 하지만, 때에 따라 적절한 리턴값이 없을 수 있는, 다음과 같은 함수들을 상상해보자.
(A) double sqrt(double n );
(B) char get_async_input();
(C) point polygon::get_any_point_effectively_inside();
첫번째 경우는 잘못된 입력의 경우에만 결과값이 존재하지 않으므로 assert나 예외 발생 등으로 해결할 수 있다. 하지만 (B)와 (C)의 경우에는 에러 상황이 아니므로 그러한 처리가 부적절하다. std::pair<T,bool>와 같이 리턴하게 하여 결과 유효성 여부를 따로 알려주는 방법도 가능하지만, 효율적이지 않을 뿐더러 어색하다. 이 때 유용하게 쓰일 수 있는 것이 바로 이 optional 라이브러리이다.
간단히, optional<T>는 T 타입의 변수가 적절히 초기화되었는지 초기화되었는지 여부를 공식적인 정보로 가지고 있으며, 적절히 초기화된 경우 그냥 T 타입인 것처럼 사용할 수 있게 해주는 클래스라 할 수 있다. 다음 예들을 살펴보면 쉽게 이해가 갈 것이다.

* 부차적 리턴값

optional<char> get_async_input()
{
if ( !queue.empty() )
return optional<char>(queue.top());
else return optional<char>(); // uninitialized
}

void receive_async_message()
{
optional<char> rcv ;
// The safe boolean conversion from 'rcv' is used here.
while ( (rcv = get_async_input()) && !timeout() )
output(*rcv);
}
  • 부차적 로컬 변수
optional<string> name ;
if ( database.open() )
{
name.reset ( database.lookup(employer_name) ) ;
}
else
{
if ( can_ask_user )
name.reset ( user.ask(employer_name) ) ;
}

if ( name )
print(*name);
else print("employer's name not found!");
  • 부차적 데이터 멤버
class figure
{
public:

figure()
{
// data member 'm_clipping_rect' is uninitialized at this point.
}

void clip_in_rect ( rect const& rect )
{
....
m_clipping_rect.reset ( rect ) ; // initialized here.
}

void draw ( canvas& cvs )
{
if ( m_clipping_rect )
do_clipping(*m_clipping_rect);

cvs.drawXXX(..);
}

// this can return NULL.
rect const* get_clipping_rect() { return get_pointer(m_clipping_rect); }

private :

optional<rect> m_clipping_rect ;

};
  • 불필요한, 값비싼 디폴트 생성 피하기
class ExpensiveCtor { ... } ;
class Fred
{
Fred() : mLargeVector(10000) {}

std::vector< optional<ExpensiveCtor> > mLargeVector ;
};



* program_options
입출력 섹션 참조

* timer
아주 간단한 타이머 클래스 라이브러리. 셋의 클래스가 제공된다. 간단한 프로파일링이나 디버깅 용도로 유용할 수 있을듯하다. 다음의 간단한 명세 및 예제로 설명을 대신한다.

#include <boost/timer.hpp>
namespace boost {
class timer {
public:
timer(); // postcondition: elapsed()==0
// compiler generated copy constructor, copy assignment, and dtor apply
void restart(); // post: elapsed()==0
double elapsed() const; // return elapsed time in seconds

double elapsed_max() const; // return estimated maximum value for elapsed()
// Portability warning: elapsed_max() may return too high a value on systems
// where std::clock_t overflows or resets at surprising values.

double elapsed_min() const; // return minimum value for elapsed()
}; // timer
} // namespace boost

 

#include <boost/progress.hpp>
int main()
{
progress_timer t; // start timing
// do something ...
return 0;
}
// Which will produce some appropriate output, for example:
// 1.23 s

 

#include <boost/progress.hpp>
namespace boost {
class progress_timer : public timer, noncopyable {
public:
progress_timer();
progress_timer( std::ostream& os ); // os is hint; implementation may ignore
~progress_timer();
}; // progress_display
} // namespace boost

 

progress_display show_progress( big_map.size() );
for ( big_map_t::iterator itr = big_map:begin();
itr != big_map.end(); ++itr )
{
// do the computation
...
++show_progress;
}
// After 70% of the elements have been processed,
// the display might look something like this:
// 0% 10 20 30 40 50 60 70 80 90 100%
// |----|----|----|----|----|----|----|----|----|----|
// ************************************

 

#include <boost/progress.hpp>
namespace boost {
class progress_display : noncopyable {
public:
progress_display( unsigned long expected_count );
// Effects: restart(expected_count)

progress_display( unsigned long expected_count,
std::ostream& os, // os is hint; implementation may ignore
const std::string & s1 = "n", //leading strings
const std::string & s2 = "",
const std::string & s3 = "" )
// Effects: save copy of leading strings, restart(expected_count)

void restart( unsigned long expected_count );
// Effects: display appropriate scale on three lines,
// prefaced by stored copy of s1, s2, s3, respectively, from constructor
// Postconditions: count()==0, expected_count()==expected_count

unsigned long operator+=( unsigned long increment )
// Effects: Display appropriate progress tic if needed.
// Postconditions: count()== original count() + increment
// Returns: count().

unsigned long operator++()
// Returns: operator+=( 1 ).

unsigned long count() const
// Returns: The internal count.

unsigned long expected_count() const
// Returns: The expected_count from the constructor.

}; // progress_display
} // namespace boost



* utility
알고리즘 섹션 참조

History

Last edited on 04/23/2007 11:59 by all2one

Comments (0)

You must log in to leave a comment. Please sign in.