BoostBoost 라이브러리의 내부 구조라이브러리의 내부 구조
에 사용된 기법을 응용하기에 사용된 기법을 응용하기
KOG, 서진택
jintaeks@gmail.com
2015 년 11 월 11 일
발표자 , jintaeks@gmail.com
 2000 년 ㈜ Kog 입사
– 프로그래머로 게임 개발 시작
 ~2015 년 9 월 ㈜ Kog 프로그래머
– 프로그래머로 게임 개발 및 기술 지원 프로그래밍
– 참여 게임
• 와일드랠리
• 범퍼킹재퍼
• 엘소드
• 얼티밋레이스
 저서
– 게임개발자를 위한 C++, 민프레스 , 2003
– MFC 의 구조와 원리 , 한빛미디어 , 2005
2
발표순서
 base-from-member idiom
 BOOST_FOREACH 의 내부 구조
– if-for combination idiom
– RAII idiom
– int-to-type idiom
– BOOST_TYPEOF
 boost::shared_ptr 의 내부 구조
– copy-and-swap idiom
– safe-bool idiom
 Q&A
3
 이 발표는 boost 라이브러리에서 가장 많이
사용하는 BOOST_FOREACH() 와
shared_ptr 의 내부 구조를 분석합니다 .
 그리고 바닥부터 해당 기능을 구현합니다 .
 이 과정을 통해서 boost 가 사용한 기법들
을 프로그래밍에 응용하는 방법을 제시합
니다 .
base-from-member: 문제 상황
class KObject
{
public:
KObject()
{
m_iInitialized = 99;
}
int GetInitialized() const
{
return m_iInitialized;
}
private:
int m_iInitialized;
};//KObject
4
class KBase
{
public:
KBase( KObject& obj_ )
{
std::cout << obj_.GetInitialized() << std::endl;
}
};//class KBase
 상속받은 클래스의 멤버를 베이스 클래스
가 접근하는 경우가 있습니다 .
 KBase 의 생성자에서 m_object 의 멤버를
호출하는 것은 객체가 아직 초기화되지 않
았으므로 안전하지 않습니다 .
base-from-member: 해결방법
struct KPrivateBase
{
KObject m_object;
};//struct KPrivateBase
class KDerived2 : protected KPrivateBase, public KBase
{
public:
KDerived2() : KBase( m_object )
{
}
private:
//KObject m_object;
};//class KDerived
5
 base-from-member idiom 은 문제를 해결하
기 위해 다중 상속에서 호출되는 클래스 생
성자의 순서를 이용합니다 .
 boost 의 많은 곳에서는 , 베이스 클래스의
생성자에서 완전히 초기화된 객체를 접근
하기 위해 이 방법을 사용합니다 .
BOOST_FOREACH 구현에 필요한 idiom
6
문제제기 : BOOST_FOREACH
std::vector<int> vecInt;
BOOST_FOREACH( int& a, vecInt )
{
}
7
 어떻게 매크로에 사용한 변수의 범위를 { 와 }
로 제한하는 것이 가능할까요 ?
관찰 : if 의 조건문에 사용한 변수
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
#include <boost/foreach.hpp>
#include <boost/typeof/typeof.hpp>
void main()
{
if( int iBlockScope = 0 ) {}
else
{
iBlockScope = 3;
std::cout << iBlockScope << std::endl;
}
//std::cout << iBlockScope << std::endl; // invalid access
}//main()
8
 if 문에 선언된 변수는 if 블록의 하부에서 사용
할 수 있습니다 . 또한 if 블록을 빠져 나가면 접
근할 수 없습니다 .
 즉 if 문에 선언된 변수의 범위 scope 는 if- 블록
안으로 제한됩니다 .
 변수를 true 가 아닌 값으로 초기화하면 , else-
블록에서 접근하는 것이 가능합니다 .
관찰 : for 에 사용한 문장
#include <iostream>
void main()
{
if( int iBlockScope = 0 ) {}
else
for( printf( "hello worldrn" ); iBlockScope == 0; iBlockScope = 1 )
{
iBlockScope = 3;
std::cout << iBlockScope << std::endl;
}
/**
hello world
3
계속하려면 아무 키나 누르십시오 . . .
*/
}//main()
9
 for 문의 첫번째 문장에는 조건에 상관없이
자유롭게 문장 statement 을 적을 수 있습
니다 . 또한 for 문에서 선언된 변수는
block scope 를 가집니다 .
관찰 : { 와 } 사용이 가능한 RAII 매크로 정의
#include <iostream>
#define RAII( statement_ ) 
if( int iBlockScope = 0 ) {} 
else 
for( statement_; iBlockScope == 0; iBlockScope = 1 )
void main()
{
RAII( printf( "hello worldrn" ) )
{
iBlockScope = 3;
std::cout << iBlockScope << std::endl;
}//RAII()
/**
hello world
3
계속하려면 아무 키나 누르십시오 . . .
*/
}//main() 10
 RAII 는 초기화 initialize 코드와 마무리
finalize 코드가 자동으로 호출되도록 하
는 디자인 패턴입니다 . RAII 는 코드를 안
전하고 깔끔하게 만듭니다 .
 if-for 문장을 조합하면 , 블록 scope 를
가지는 변수를 , { 안에서만 사용하도록
매크로를 정의할 수 있습니다 .
int2type
template <int I>
struct Int2Type
{
enum { value = I };
};
template <class T, unsigned int N>
class Array : public std::array <T, N>
{
enum AlgoType { NOOP, INSERTION_SORT,
QUICK_SORT };
static const int algo = (N==0) ? NOOP
: (N==1) ? NOOP
: (N<50) ? INSERTION_SORT : QUICK_SORT;
void sort (Int2Type<NOOP>) {}
void sort (Int2Type<INSERTION_SORT>) {}
void sort (Int2Type<QUICK_SORT>) {}
public:
void sort()
{
sort (Int2Type<algo>());
}
11
int main(void)
{
Array<int, 1> a;
a.sort(); // No-op!
Array<int, 400> b;
b.sort(); // Quick sort
}
 컴파일 시간에 정수 상수를 타입
으로 취급하도록 합니다 .
관찰 : sizeof 에 표현식 사용
#include <iostream>
#include <vector>
#include <boost/typeof/typeof.hpp>
#include <boost/mpl/int.hpp>
template<class T>
T type_encoder( T expression_ )
{
return expression_;
}
template<int ID>
struct typeid_wrapper
{
typedef typename extract_type<int_<ID> >::id2type id2type;
typedef typename id2type::type type;
};
12
 함수 템플릿은 파라미터로 전달
된 표현식의 타입을 자동으로
유추하는 역할로 사용할 수 있
습니다 .
 id 로 구분되는 유일한 타입을 생성하고 ,
typeid_wrapper<>::type 으로 접근합니다 .
관찰 : BOOST_TYPEOF 의 원리
#include <iostream>
#include <vector>
#include <boost/typeof/typeof.hpp>
#include <boost/mpl/int.hpp>
…
void main()
{
int i = 1;
sizeof( type_encoder(i+3) );
typeid_wrapper<sizeof( type_encoder(i+3) ) >::type k;
BOOST_TYPEOF( i + 3 ) j;
j = 3;
std::cout << i << std::endl;
std::cout << j << std::endl;
}//main()
13
 sizeof() 에는 type 뿐만 아니라 unary
expression 을 전달할 수 있습니다 .
 함수 호출과 sizeof() 를 이용하면 표현식을
템플릿에 전달할 수 있습니다 .
 이런 원리를 사용하여 구현된 , BOOST_TYPEOF() 는
표현식의 타입을 구합니다 ! i+3 의 타입이 int 이므로
int j; 와 같은 문장입니다 .
응용 예 : KLOCK_BEGIN
KLOCK_DECLARE_INIT( KTest, m_int, (0) );
 
void main()
{
KLOCK_DECLARE( std::vector<int>, vecUserInfo );
std::cout << vecUserInfo.size() << std::endl;
 
//vecUserInfo.push_back( 3 ); // compile time error
//m_int.SetInt( 5 ); // compile time error
 
KLOCK_BEGIN( m_int )
KLOCK_BEGIN( vecUserInfo )
{
m_int.SetInt( 5 );
vecUserInfo.push_back( 5 );
}
std::cout << m_int.GetInt() << std::endl;
std::cout << vecUserInfo.size() << std::endl;
}
 14
 특정한 멤버 변수를 읽는 것은 허용하
되 , 쓰는 동작은 에러가 나도록 할 수
있습니다 .
 KLOCK_DECLARE() 로 선언한 변수는
이러한 특징을 가지도록 합니다 .
 KLOCK_BEGIN() 으로 특정한 변수에
대한 쓰기 권한을 허용할 수 있습니다 .
 이러한 기법은 읽기와 쓰기 동작을 구
분하는 코드를 작성하도록 합니다 .
 변수를 변경할 때 실행되어야 하는 코
드를 지정함으로써 , 여러가지
housekeeping 동작을 숨길 수 있습니
다 .
FOREACH 구현
15
Step1: for- 문장
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
#include <boost/foreach.hpp>
void main()
{
std::vector<int> vecIntData;
vecIntData.push_back( 1 );
vecIntData.push_back( 3 );
for( std::vector<int>::iterator vitor = vecIntData.begin();
vitor != vecIntData.end();
++vitor )
{
int& iData = *vitor;
std::cout << iData << std::endl;
}//for
}//main()
16
 for 문을 이용하여 vector 를
iteration 하는 코드를 작성했습니
다 .
Step2: set_true() 정의하기
bool set_true( bool& bInOutVariable_ )
{
bInOutVariable_ = true;
return false;
}
void main()
{
std::vector<int> vecIntData;
vecIntData.push_back( 1 );
vecIntData.push_back( 3 );
…
17
 set_true() 는 항상 false 를 리턴합니
다 . 인자로 전달된 bool 변수를
true 로 만드는 역할을 합니다 .
Step3: use BOOST_TYPEOF
…
//BOOST_FOREACH( int& iData, vecIntData )
if( bool bForBreaker = false ) {}
else
for( BOOST_TYPEOF(vecIntData)::iterator vitor = vecIntData.begin()
; bForBreaker == false && vitor != vecIntData.end()
; ++vitor )
{
if( set_true(bForBreaker) ) {}
else
for( int& iData = *vitor; bForBreaker == true; bForBreaker = false )
{
std::cout << iData << std::endl;
//break;
}//for
}//for
}//main()
18
 set_true() 는 변수를 항상 true 로 설정하지만 ,
false 를 리턴하므로 , 변수값을 변경하는 일만 합
니다 .
 set_true() 가 필요한 이유는 최종적으로 정의한 매
크로 안에서 break 를 사용했을 때 , break 문장이
바깥 for- 문장을 빠져나가도록 하기 위함입니다 .
Step4: { 를 제거하기
void main()
{
std::vector<int> vecIntData;
vecIntData.push_back( 1 );
vecIntData.push_back( 3 );
//BOOST_FOREACH( int& iData, vecIntData )
if( bool bForBreaker = false ) {}
else
for( BOOST_TYPEOF(vecIntData)::iterator vitor = vecIntData.begin()
; bForBreaker == false && vitor != vecIntData.end()
; ++vitor )
if( set_true(bForBreaker) ) {}
else
for( int& iData = *vitor; bForBreaker == true; bForBreaker = false )
std::cout << iData << std::endl;
}//main()
19
 BOOST_TYPEOF() 를 사용하여 매크
로로 전달된 표현식의 iterator 를 접근
합니다 .
 RAII 구현을 위해서 { 와 } 는 포함되
지 않게 매크로 작성을 준비합니다 .
FOREACH 완성
#define FOREACH( statement_, container_ ) 
if( bool bForBreaker = false ) {} 
else 
for( BOOST_TYPEOF(container_)::iterator vitor = container_.begin() 
; bForBreaker == false && vitor != container_.end() 
; ++vitor ) 
if( set_true(bForBreaker) ) {} 
else 
for( statement_ = *vitor; bForBreaker == true; bForBreaker = false )
void main()
{
std::vector<int> vecIntData;
vecIntData.push_back( 1 );
vecIntData.push_back( 3 );
FOREACH( int& iData, vecIntData )
{
std::cout << iData << std::endl;
//break;
}//FOREACH()
}//main()
20
 중첩된 FOREACH() 와 배열을 지원하기
위해서는 추가적인 처리가 필요합니다 .
응용 예 : ASSET_FOREACH
 기본적으로 구현한 FOREACH() 를 응용하여 , 프로그
램의 내부 로직에 의존적인 여러가지 매크로를 정의
할 수 있습니다 .
– 프로그램에서 현재 관리중인 모든 asset 들에 대한 커스텀
iteration 을 작성할 수 있습니다 .
– .xml 파일을 로드하고 나서 , 모든 element 에 대한 커스텀
iteration 을 작성할 수 있습니다 .
– 프로그램에서 현재 생성된 모든 게임 객체에 대한 커스텀 iteration
을 작성할 수 있습니다 .
21
shared_ptr 의 내부 구조
22
void 참조를 특화로 구현
23
template<class T> struct shared_ptr_traits
{
typedef T& reference;
};
template<> struct shared_ptr_traits<void>
{
typedef void reference;
};
 void 타입에 대한 reference 가 가능하도록
템플릿 특화 specialization 을 사용하여 미
리 구현해 둡니다 .
 기타 type-trait 에 대한 설명은 생략합니다 .
shared_ptr: 생성자 구현
24
template<class T> class shared_ptr
{
public:
typedef shared_ptr<T> this_type;
typedef T value_type;
typedef T* pointer;
typedef typename shared_ptr_traits<T>::reference reference;
public:
shared_ptr(T * p = 0) : px(p), pn(0)
{
if( px != NULL )
pn = new int(1);
}
 생성자에서는 전달받은 raw pointer 를 초
기화합니다 .
 참조 카운팅을 위한 메모리 할당을 하고 ,
참조 카운터를 1 로 초기화합니다 .
shared_ptr: 복사 생성자와 파괴자 구현
25
shared_ptr( const shared_ptr& right_ ) : px( 0 ), pn( 0 )
{
release();
px = right_.px;
pn = right_.pn;
if( px != NULL )
*pn += 1;
}
~shared_ptr()
{
release();
}
 복사 생성자는 먼저 이전에 할당되어 있는
리소스를 해제 합니다 .
 새로운 리소스에 대한 참조를 설정하고 ,
참조 카운터를 증가시킵니다 .
shared_ptr: operator=(), operator pointer()
26
shared_ptr& operator=( const shared_ptr& right_ )
{
release();
px = right_.px;
pn = right_.pn;
if( px != NULL )
*pn += 1;
return *this;
}
operator pointer() const
{
return px;
}
 대입 연산자는 복사 생성자와 거의 동일하
게 구현합니다 .
 타입에 대한 포인터 연산이 제대로 동작하
도록 operator T*() 를 정의합니다 .
shared_ptr: release() 구현
27
void release()
{
if( px != NULL && *pn >= 1 )
{
*pn -= 1;
if( *pn == 0 )
{
delete px;
px = NULL;
delete pn;
pn = NULL;
}//if
}//if
px = NULL;
pn = NULL;
}
 release() 는 먼저 참조 카운터를 감소합니
다 .
 참조 카운터가 0 이되면 , 실제 리소스를
해제 합니다 .
shared_ptr: reset(), use_count() 구현
28
void reset()
{
release();
}
void reset(T * p)
{
release();
px = p;
pn = NULL;
if( px != NULL )
pn = new int(1);
}
int use_count() const { return *pn; }
 reset() 은 해제 후 할당과 동일합니다 .
 완성된 shared_ptr 은 암시적 생성 implicit
construction 을 지원하지 않으므로 , reset()
구현이 반드시 필요합니다 .
 use_count() 는 현재 참조 카운터를 리턴합
니다 .
shared_ptr: operator*(), operator->() 구현
29
reference operator*() const // never throws
{
return *px;
}
T* operator->() const // never throws
{
return px;
}
private:
T* px;
int* pn;
};//template<class T> class shared_ptr
 간접지정 연산자와 화살표 연산자가 제대
로 동작하도록 함수를 작성합니다 .
 void* 에 대한 참조가 동작하도록 하기 위해
서는 void 에 대한 참조를 템플릿 특화로 미
리 구현해 두어야 합니다 .
main() 에서 테스트
30
int main()
{
typedef shared_ptr<int> IntPtr;
IntPtr spInt = new int(3); // spInt.use_count() == 1
if( spInt != NULL )
{
std::cout << *spInt << std::endl; // 3
}//if
IntPtr spInt2 = spInt; // spInt.use_count() == 2
IntPtr spInt3 = spInt; // spInt.use_count() == 3
spInt.reset( new int(4) ); // spInt.use_count() == 1
*spInt = 5; // 3 changed to 5
if( spInt2 != NULL ) // spInt2.use_count() == 2
{
std::cout << *spInt2 << std::endl; // 3
}//if
return 0;
}//int main()
copy-and-swap: swap() 메서드 구현
31
void swap( shared_ptr<T>& right_ ) // never throws
{
std::swap( px, right_.px );
std::swap( pn, right_.pn );
}
 exception safety
1) Basic: component 의 invariant 는 보존되고 , resource
leak 은 발생하지 않아야 합니다 .
2) Strong: 성공적으로 완료되었던지 예외를 던졌던
지 둘 중의 하나여야 합니다 .
3) No-throw: 예외를 던지지 않아야 합니다 .
 copy-and-swap 을 지원하기 위해서 shared_ptr 의
swap() 은 예외를 던지지 않도록 구현해야 합니다 .
shared_ptr: swap() 을 반영한 수정
32
shared_ptr& operator=( const shared_ptr& right_ )
{
//release();
//px = right_.px;
//pn = right_.pn;
//if( px != NULL )
// *pn += 1;
this_type(right_).swap(*this);
return *this;
}
void reset(T * p)
{
//release();
//px = p;
//pn = NULL;
//if( px != NULL )
// pn = new int(1);
this_type(p).swap(*this);
}
관찰 : if 의 조건문 표현식
33
class KBoolTest
{
public:
operator bool() const { return true; }
operator int() const { return 1; }
operator int*() const { return NULL; }
int GetValue() const { return 9; }
};
int main()
{
KBoolTest t;
if( t )
std::cout << t.GetValue() << std::endl;
return 0;
}//int main()
 if 문의 조건은 일반적인 bool 표현식이 아닌
C 가 허용하는 참인 조건식을 적을 수 있습니다
.
 평가의 partial order 의 순서는 bool  native
type  pointer 의 순입니다 .
safe bool idiom
34
//int* p = spInt; // (1)
//if( spInt < spInt ) // (2)
//{
//}  지금까지의 구현은 (1) 과 (2) 처럼 잘못된 사
용이나 , 의미없는 bool 표현식을 막지 못
합니다 .
  template <typename T> void some_func(const T& t) {
    if (t)   
      t->print();
  }
 smart pointer T 에 대해 이 문장이 동작
하려면 T 는 bool 형에 대한 형 변환 연
산자를 제공해야 합니다 .
해결시도 1: operator bool() 구현
35
class Testable {
    bool ok_;
  public:
    explicit Testable(bool b=true):ok_(b) {}
 
    operator bool() const {
      return ok_;
    }
  };  shared_ptr 은 자신이 valid 한 raw pointer 를 가질 때 ,
true 를 리턴하는 operator bool() 을 작성할 수 있습니다 .
operator bool() cont.
36
test << 1; // (1)
  int i=test; // (2)
  Testable a;
  AnotherTestable b;
 
  if (a==b) { // (3)
  }
 
  if (a<b) { // (4)
  }
 이 구현은 (1), (2) 같은 의미 없는 문장
이나 , (3), (4) 같은 의미 없는 bool 검
사를 막지 못합니다 .
해결시도 2: operator void*() 구현
37
 operator void*() const {
    return ok_==true ? this : 0;
  }
 이 구현은 영리해 보이지만 , 아래의 문장처럼
delete 를 직접호출 할 수 있는 약점을 가집니다 .
Testable test;
delete test;
해결시도 3: nested class
38
class Testable {
    bool ok_;
  public:
    explicit Testable(bool b=true):ok_(b) {}
 
    class nested_class;
 
    operator const nested_class*() const {
      return ok_ ? reinterpret_cast<const nested_class*>(this) : 0;
    }
  };
Testable b1,b2;
 
  if (b1==b2) {
  }
 
  if (b1<b2) {
  }
 safe bool 을 위한 nested class 구현 역시 의미없는 bool 검
사를 막지 못합니다 .
 이 문제를 해결하려면 포인터 형변환이 되지만 , < 같은 비교
연산자를 지원하지 않는 데이터 타입을 사용하는 것입니다 .
 흥미롭게도 멤버 함수에 대한 포인터가 그렇게 동작합
니다 !
최종 버전 : safe bool idiom
39
class Testable {
    bool ok_;
    typedef void (Testable::*bool_type)() const;
    void this_type_does_not_support_comparisons() const {}
  public:
    explicit Testable(bool b=true):ok_(b) {}
 
    operator bool_type() const {
      return ok_==true ?
        &Testable::this_type_does_not_support_comparisons : 0;
    }
  };
 if- 문장의 조건 표현식에 포인터가 사용될 수
있는 점을 이용하여 , 함수 포인터를 리턴하
는 연산자 함수를 정의합니다 .
shared_ptr 의 내부 : unspecified_bool_type 사용
40
//operator pointer() const
//{
// return px;
//}
void unspecified_bool() const
{
}
typedef void (shared_ptr::*unspecified_bool_type)() const;
operator unspecified_bool_type() const // never throws
{
return px == 0 ? 0 : &shared_ptr::unspecified_bool;
}
 boost 의 실제 코드는
unspecified_bool_type() 을 사용합니다 .
 C++ 표준 라이브러리 뿐만 아니라 , 게임
엔진의 스마트 포인터 구현은 모두 이러한
기법을 사용합니다 .
shared_ptr 에 적용된 safe bool idiom
41
typedef shared_ptr<int> IntPtr;
IntPtr spInt = new int(3);
IntPtr spInt2 = spInt;
IntPtr spInt3 = spInt;
spInt.reset( new int(4) );
*spInt = 5;
if( spInt2 != NULL )
{
std::cout << *spInt << std::endl;
}//if
int* p = spInt; // (1) error
if( spInt2 < spInt3 ) // (2) error
{
}
 이제 (1) 과 (2) 같은 의미 없는 문
장은 컴파일 시간 에러가 발생합
니다 .
implicit constructor 문제 해결하기
42
void Test( shared_ptr<int> spInt_ )
{
}
int iData = 5;
Test( &iData );
 실제 포인터가 아닌 , 포인터 표현식에 대한
shared_ptr 생성을 막을 필요가 있습니다 .
 이 문제에 대한 해결방법은 명시적 생성만 가능하도록
클래스를 설계하는 것입니다 .
explicit shared_ptr(T * p = 0) : px(p), pn(0)
{
if( px != NULL )
pn = new int(1);
}
boost::weak_ptr 과 RAII 결합
43
weak_ptr<T>
44
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
class Test;
typedef boost::shared_ptr<Test> TestPtr;
typedef boost::weak_ptr<Test> TestWeakPtr;
class Test
{
public:
 shared_ptr 을 제공할 때 , 포인터 순환 참
조 문제등을 해결하기 위해서 weak_ptr 을
함께 제공합니다 .
weak_ptr<T> cont.
45
class TestRef
{
public:
TestRef(){}
void SetTest( TestPtr tp )
{
m_tp = tp;
}
void Print() const
{
if( TestPtr tp = m_tp.lock() )
{
tp->Print();
}
}
private:
TestWeakPtr m_tp;
};
 weak_ptr 을 사용하는 경우 , 지금 사용해
도 안전한 실제 포인터를 얻기 위해서
lock() 을 호출합니다 .
int main()
{
TestPtr tp = Test::CreateTest( 1 );
TestRef ref;
ref.SetTest( tp );
tp.reset();
ref.Print();
}//int main()
weak_ptr<T> lock()
46
 weak_ptr<T> 의 lock() 구현
shared_ptr<T, A, D> lock() const // never throws
{
// optimization: avoid throw overhead
if(expired()){
return shared_ptr<element_type, A, D>();
}
BOOST_TRY{
return shared_ptr<element_type, A, D>(*this);
}
}
 shared_ptr<T>( weak_ptr<Y> ) 구현
template<class Y>
explicit shared_ptr(weak_ptr<Y> const & r): pn(r.pn) // may throw
{
// it is now safe to copy r.px, as pn(r.pn) did not throw
px = r.px;
}
raw pointer 에서 shared_ptr 얻기 : factory
47
class X
{
private:
X() { ... }
public:
static shared_ptr<X> create()
{
shared_ptr<X> px(new X);
// use px as 'this_'
return px;
}
};
 shared_ptr<T> 로 관리되지 않는 raw 포
인터에서 shared_ptr<T> 을 얻을 필요가
있습니다 .
 클래스 객체가 shared_ptr<T> 로 관리되
더라도 , 생성자에서 shared_ptr<T> 를 얻
을 방법도 없습니다 .
 해결 방법은 weak_ptr 과 팩토리 함수를
사용하는 것입니다 .
factory: this 에 대한 shared_ptr<T>
48
class impl: public X, public Y
{
private:
weak_ptr<impl> weak_this;
impl(impl const &);
impl & operator=(impl const &);
impl() { ... }
public:
static shared_ptr<impl> create()
{
shared_ptr<impl> pi(new impl);
pi->weak_this = pi; // (1) 팩토리에서 weak_ptr<T> 을 초기화 해 놓습니다 .
return pi;
}
virtual void f() { ... }
virtual shared_ptr<X> getX()
{
shared_ptr<X> px(weak_this);
return px;
}
};
 대상 객체를 생성할 때 , weak_ptr 이 적절
하게 초기화되도록 합니다 .
 그러면 , 후에 필요하면 대상 객체에 대한
shared_ptr 을 얻을 수 있습니다 .
boost::enable_shared_from_this<T>
49
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
class Test;
typedef boost::shared_ptr<Test> TestPtr;
class Test : public boost::enable_shared_from_this<Test>
{
public:
static TestPtr CreateTest(int i)
{
TestPtr sp( new Test( i ) );
return sp;
}
Test(int i) : m_i(i) {}
void Print() const
{
std::cout << m_i << std::endl;
}
private:
int m_i;
};
 이 역할을 하는 boost 의 베이스 클래스가
enabled_shared_from_this 입니다 .
shared_from_this()
50
void TestFun( Test* pt )
{
TestPtr tp;
if( pt != NULL )
tp = pt->shared_from_this();
if( tp != NULL )
{
tp->Print();
}
}
 Test 가 enable_shared_from_this 를 상속받은 경ㅇ
우 , raw pointer 에서 shared_ptr 을 얻기 위해서는
shared_from_this() 를 호출합니다 .
응용 예 : RAII
51
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/typeof/typeof.hpp>
#define KSmartPointer(type_) 
class type_; 
typedef boost::shared_ptr<type_> type_##SharedPtr; 
typedef boost::weak_ptr<type_> type_##WeakPtr;
#define IF_MEMBER_WEAKPTR( member_name_ )
if( boost::shared_ptr<BOOST_TYPEOF(member_name_)::element_type> member_name_ = this-
>member_name_.lock() )
 게임 객체가 관리되는 리소스가 있는 경우에만 사용하려
고 하는 경우 , 리소스에 대한 weak_ptr 을 멤버로 가집니
다 .
 게임 객체가 실제 객체를 사용하려고 하면 , lock() 호출이
필요합니다 .
 이러한 패턴을 RAII 매크로로 작성했습니다 .
응용 예 cont.
52
class TestContainer
{
public:
TestContainer(){}
void SetTest( TestSharedPtr tp )
{
m_tp = tp;
}
void Print() const
{
IF_MEMBER_WEAKPTR( m_tp )
{
m_tp->Print();
}//if
}
private:
TestWeakPtr m_tp;
};
 IF_MEMBER_WEAKPTR() 은 RAII 스타일의 매크
로로 멤버에 대한 안전한 접근을 보장합니다 .
질문과 대답
53
참고자료
 http://www.boost.org/doc/libs/1_35_0/libs/type_tra
 http://www.boost.org/doc/libs/1_37_0/libs/smart_pt
 http://www.artima.com/cppsource/safebool.html
54

Boost라이브러리의내부구조 20151111 서진택

  • 1.
    BoostBoost 라이브러리의 내부구조라이브러리의 내부 구조 에 사용된 기법을 응용하기에 사용된 기법을 응용하기 KOG, 서진택 jintaeks@gmail.com 2015 년 11 월 11 일
  • 2.
    발표자 , jintaeks@gmail.com 2000 년 ㈜ Kog 입사 – 프로그래머로 게임 개발 시작  ~2015 년 9 월 ㈜ Kog 프로그래머 – 프로그래머로 게임 개발 및 기술 지원 프로그래밍 – 참여 게임 • 와일드랠리 • 범퍼킹재퍼 • 엘소드 • 얼티밋레이스  저서 – 게임개발자를 위한 C++, 민프레스 , 2003 – MFC 의 구조와 원리 , 한빛미디어 , 2005 2
  • 3.
    발표순서  base-from-member idiom BOOST_FOREACH 의 내부 구조 – if-for combination idiom – RAII idiom – int-to-type idiom – BOOST_TYPEOF  boost::shared_ptr 의 내부 구조 – copy-and-swap idiom – safe-bool idiom  Q&A 3  이 발표는 boost 라이브러리에서 가장 많이 사용하는 BOOST_FOREACH() 와 shared_ptr 의 내부 구조를 분석합니다 .  그리고 바닥부터 해당 기능을 구현합니다 .  이 과정을 통해서 boost 가 사용한 기법들 을 프로그래밍에 응용하는 방법을 제시합 니다 .
  • 4.
    base-from-member: 문제 상황 classKObject { public: KObject() { m_iInitialized = 99; } int GetInitialized() const { return m_iInitialized; } private: int m_iInitialized; };//KObject 4 class KBase { public: KBase( KObject& obj_ ) { std::cout << obj_.GetInitialized() << std::endl; } };//class KBase  상속받은 클래스의 멤버를 베이스 클래스 가 접근하는 경우가 있습니다 .  KBase 의 생성자에서 m_object 의 멤버를 호출하는 것은 객체가 아직 초기화되지 않 았으므로 안전하지 않습니다 .
  • 5.
    base-from-member: 해결방법 struct KPrivateBase { KObjectm_object; };//struct KPrivateBase class KDerived2 : protected KPrivateBase, public KBase { public: KDerived2() : KBase( m_object ) { } private: //KObject m_object; };//class KDerived 5  base-from-member idiom 은 문제를 해결하 기 위해 다중 상속에서 호출되는 클래스 생 성자의 순서를 이용합니다 .  boost 의 많은 곳에서는 , 베이스 클래스의 생성자에서 완전히 초기화된 객체를 접근 하기 위해 이 방법을 사용합니다 .
  • 6.
  • 7.
    문제제기 : BOOST_FOREACH std::vector<int>vecInt; BOOST_FOREACH( int& a, vecInt ) { } 7  어떻게 매크로에 사용한 변수의 범위를 { 와 } 로 제한하는 것이 가능할까요 ?
  • 8.
    관찰 : if의 조건문에 사용한 변수 #include <iostream> #include <algorithm> #include <functional> #include <vector> #include <boost/foreach.hpp> #include <boost/typeof/typeof.hpp> void main() { if( int iBlockScope = 0 ) {} else { iBlockScope = 3; std::cout << iBlockScope << std::endl; } //std::cout << iBlockScope << std::endl; // invalid access }//main() 8  if 문에 선언된 변수는 if 블록의 하부에서 사용 할 수 있습니다 . 또한 if 블록을 빠져 나가면 접 근할 수 없습니다 .  즉 if 문에 선언된 변수의 범위 scope 는 if- 블록 안으로 제한됩니다 .  변수를 true 가 아닌 값으로 초기화하면 , else- 블록에서 접근하는 것이 가능합니다 .
  • 9.
    관찰 : for에 사용한 문장 #include <iostream> void main() { if( int iBlockScope = 0 ) {} else for( printf( "hello worldrn" ); iBlockScope == 0; iBlockScope = 1 ) { iBlockScope = 3; std::cout << iBlockScope << std::endl; } /** hello world 3 계속하려면 아무 키나 누르십시오 . . . */ }//main() 9  for 문의 첫번째 문장에는 조건에 상관없이 자유롭게 문장 statement 을 적을 수 있습 니다 . 또한 for 문에서 선언된 변수는 block scope 를 가집니다 .
  • 10.
    관찰 : {와 } 사용이 가능한 RAII 매크로 정의 #include <iostream> #define RAII( statement_ ) if( int iBlockScope = 0 ) {} else for( statement_; iBlockScope == 0; iBlockScope = 1 ) void main() { RAII( printf( "hello worldrn" ) ) { iBlockScope = 3; std::cout << iBlockScope << std::endl; }//RAII() /** hello world 3 계속하려면 아무 키나 누르십시오 . . . */ }//main() 10  RAII 는 초기화 initialize 코드와 마무리 finalize 코드가 자동으로 호출되도록 하 는 디자인 패턴입니다 . RAII 는 코드를 안 전하고 깔끔하게 만듭니다 .  if-for 문장을 조합하면 , 블록 scope 를 가지는 변수를 , { 안에서만 사용하도록 매크로를 정의할 수 있습니다 .
  • 11.
    int2type template <int I> structInt2Type { enum { value = I }; }; template <class T, unsigned int N> class Array : public std::array <T, N> { enum AlgoType { NOOP, INSERTION_SORT, QUICK_SORT }; static const int algo = (N==0) ? NOOP : (N==1) ? NOOP : (N<50) ? INSERTION_SORT : QUICK_SORT; void sort (Int2Type<NOOP>) {} void sort (Int2Type<INSERTION_SORT>) {} void sort (Int2Type<QUICK_SORT>) {} public: void sort() { sort (Int2Type<algo>()); } 11 int main(void) { Array<int, 1> a; a.sort(); // No-op! Array<int, 400> b; b.sort(); // Quick sort }  컴파일 시간에 정수 상수를 타입 으로 취급하도록 합니다 .
  • 12.
    관찰 : sizeof에 표현식 사용 #include <iostream> #include <vector> #include <boost/typeof/typeof.hpp> #include <boost/mpl/int.hpp> template<class T> T type_encoder( T expression_ ) { return expression_; } template<int ID> struct typeid_wrapper { typedef typename extract_type<int_<ID> >::id2type id2type; typedef typename id2type::type type; }; 12  함수 템플릿은 파라미터로 전달 된 표현식의 타입을 자동으로 유추하는 역할로 사용할 수 있 습니다 .  id 로 구분되는 유일한 타입을 생성하고 , typeid_wrapper<>::type 으로 접근합니다 .
  • 13.
    관찰 : BOOST_TYPEOF의 원리 #include <iostream> #include <vector> #include <boost/typeof/typeof.hpp> #include <boost/mpl/int.hpp> … void main() { int i = 1; sizeof( type_encoder(i+3) ); typeid_wrapper<sizeof( type_encoder(i+3) ) >::type k; BOOST_TYPEOF( i + 3 ) j; j = 3; std::cout << i << std::endl; std::cout << j << std::endl; }//main() 13  sizeof() 에는 type 뿐만 아니라 unary expression 을 전달할 수 있습니다 .  함수 호출과 sizeof() 를 이용하면 표현식을 템플릿에 전달할 수 있습니다 .  이런 원리를 사용하여 구현된 , BOOST_TYPEOF() 는 표현식의 타입을 구합니다 ! i+3 의 타입이 int 이므로 int j; 와 같은 문장입니다 .
  • 14.
    응용 예 :KLOCK_BEGIN KLOCK_DECLARE_INIT( KTest, m_int, (0) );   void main() { KLOCK_DECLARE( std::vector<int>, vecUserInfo ); std::cout << vecUserInfo.size() << std::endl;   //vecUserInfo.push_back( 3 ); // compile time error //m_int.SetInt( 5 ); // compile time error   KLOCK_BEGIN( m_int ) KLOCK_BEGIN( vecUserInfo ) { m_int.SetInt( 5 ); vecUserInfo.push_back( 5 ); } std::cout << m_int.GetInt() << std::endl; std::cout << vecUserInfo.size() << std::endl; }  14  특정한 멤버 변수를 읽는 것은 허용하 되 , 쓰는 동작은 에러가 나도록 할 수 있습니다 .  KLOCK_DECLARE() 로 선언한 변수는 이러한 특징을 가지도록 합니다 .  KLOCK_BEGIN() 으로 특정한 변수에 대한 쓰기 권한을 허용할 수 있습니다 .  이러한 기법은 읽기와 쓰기 동작을 구 분하는 코드를 작성하도록 합니다 .  변수를 변경할 때 실행되어야 하는 코 드를 지정함으로써 , 여러가지 housekeeping 동작을 숨길 수 있습니 다 .
  • 15.
  • 16.
    Step1: for- 문장 #include<iostream> #include <algorithm> #include <functional> #include <vector> #include <boost/foreach.hpp> void main() { std::vector<int> vecIntData; vecIntData.push_back( 1 ); vecIntData.push_back( 3 ); for( std::vector<int>::iterator vitor = vecIntData.begin(); vitor != vecIntData.end(); ++vitor ) { int& iData = *vitor; std::cout << iData << std::endl; }//for }//main() 16  for 문을 이용하여 vector 를 iteration 하는 코드를 작성했습니 다 .
  • 17.
    Step2: set_true() 정의하기 boolset_true( bool& bInOutVariable_ ) { bInOutVariable_ = true; return false; } void main() { std::vector<int> vecIntData; vecIntData.push_back( 1 ); vecIntData.push_back( 3 ); … 17  set_true() 는 항상 false 를 리턴합니 다 . 인자로 전달된 bool 변수를 true 로 만드는 역할을 합니다 .
  • 18.
    Step3: use BOOST_TYPEOF … //BOOST_FOREACH(int& iData, vecIntData ) if( bool bForBreaker = false ) {} else for( BOOST_TYPEOF(vecIntData)::iterator vitor = vecIntData.begin() ; bForBreaker == false && vitor != vecIntData.end() ; ++vitor ) { if( set_true(bForBreaker) ) {} else for( int& iData = *vitor; bForBreaker == true; bForBreaker = false ) { std::cout << iData << std::endl; //break; }//for }//for }//main() 18  set_true() 는 변수를 항상 true 로 설정하지만 , false 를 리턴하므로 , 변수값을 변경하는 일만 합 니다 .  set_true() 가 필요한 이유는 최종적으로 정의한 매 크로 안에서 break 를 사용했을 때 , break 문장이 바깥 for- 문장을 빠져나가도록 하기 위함입니다 .
  • 19.
    Step4: { 를제거하기 void main() { std::vector<int> vecIntData; vecIntData.push_back( 1 ); vecIntData.push_back( 3 ); //BOOST_FOREACH( int& iData, vecIntData ) if( bool bForBreaker = false ) {} else for( BOOST_TYPEOF(vecIntData)::iterator vitor = vecIntData.begin() ; bForBreaker == false && vitor != vecIntData.end() ; ++vitor ) if( set_true(bForBreaker) ) {} else for( int& iData = *vitor; bForBreaker == true; bForBreaker = false ) std::cout << iData << std::endl; }//main() 19  BOOST_TYPEOF() 를 사용하여 매크 로로 전달된 표현식의 iterator 를 접근 합니다 .  RAII 구현을 위해서 { 와 } 는 포함되 지 않게 매크로 작성을 준비합니다 .
  • 20.
    FOREACH 완성 #define FOREACH(statement_, container_ ) if( bool bForBreaker = false ) {} else for( BOOST_TYPEOF(container_)::iterator vitor = container_.begin() ; bForBreaker == false && vitor != container_.end() ; ++vitor ) if( set_true(bForBreaker) ) {} else for( statement_ = *vitor; bForBreaker == true; bForBreaker = false ) void main() { std::vector<int> vecIntData; vecIntData.push_back( 1 ); vecIntData.push_back( 3 ); FOREACH( int& iData, vecIntData ) { std::cout << iData << std::endl; //break; }//FOREACH() }//main() 20  중첩된 FOREACH() 와 배열을 지원하기 위해서는 추가적인 처리가 필요합니다 .
  • 21.
    응용 예 :ASSET_FOREACH  기본적으로 구현한 FOREACH() 를 응용하여 , 프로그 램의 내부 로직에 의존적인 여러가지 매크로를 정의 할 수 있습니다 . – 프로그램에서 현재 관리중인 모든 asset 들에 대한 커스텀 iteration 을 작성할 수 있습니다 . – .xml 파일을 로드하고 나서 , 모든 element 에 대한 커스텀 iteration 을 작성할 수 있습니다 . – 프로그램에서 현재 생성된 모든 게임 객체에 대한 커스텀 iteration 을 작성할 수 있습니다 . 21
  • 22.
  • 23.
    void 참조를 특화로구현 23 template<class T> struct shared_ptr_traits { typedef T& reference; }; template<> struct shared_ptr_traits<void> { typedef void reference; };  void 타입에 대한 reference 가 가능하도록 템플릿 특화 specialization 을 사용하여 미 리 구현해 둡니다 .  기타 type-trait 에 대한 설명은 생략합니다 .
  • 24.
    shared_ptr: 생성자 구현 24 template<classT> class shared_ptr { public: typedef shared_ptr<T> this_type; typedef T value_type; typedef T* pointer; typedef typename shared_ptr_traits<T>::reference reference; public: shared_ptr(T * p = 0) : px(p), pn(0) { if( px != NULL ) pn = new int(1); }  생성자에서는 전달받은 raw pointer 를 초 기화합니다 .  참조 카운팅을 위한 메모리 할당을 하고 , 참조 카운터를 1 로 초기화합니다 .
  • 25.
    shared_ptr: 복사 생성자와파괴자 구현 25 shared_ptr( const shared_ptr& right_ ) : px( 0 ), pn( 0 ) { release(); px = right_.px; pn = right_.pn; if( px != NULL ) *pn += 1; } ~shared_ptr() { release(); }  복사 생성자는 먼저 이전에 할당되어 있는 리소스를 해제 합니다 .  새로운 리소스에 대한 참조를 설정하고 , 참조 카운터를 증가시킵니다 .
  • 26.
    shared_ptr: operator=(), operatorpointer() 26 shared_ptr& operator=( const shared_ptr& right_ ) { release(); px = right_.px; pn = right_.pn; if( px != NULL ) *pn += 1; return *this; } operator pointer() const { return px; }  대입 연산자는 복사 생성자와 거의 동일하 게 구현합니다 .  타입에 대한 포인터 연산이 제대로 동작하 도록 operator T*() 를 정의합니다 .
  • 27.
    shared_ptr: release() 구현 27 voidrelease() { if( px != NULL && *pn >= 1 ) { *pn -= 1; if( *pn == 0 ) { delete px; px = NULL; delete pn; pn = NULL; }//if }//if px = NULL; pn = NULL; }  release() 는 먼저 참조 카운터를 감소합니 다 .  참조 카운터가 0 이되면 , 실제 리소스를 해제 합니다 .
  • 28.
    shared_ptr: reset(), use_count()구현 28 void reset() { release(); } void reset(T * p) { release(); px = p; pn = NULL; if( px != NULL ) pn = new int(1); } int use_count() const { return *pn; }  reset() 은 해제 후 할당과 동일합니다 .  완성된 shared_ptr 은 암시적 생성 implicit construction 을 지원하지 않으므로 , reset() 구현이 반드시 필요합니다 .  use_count() 는 현재 참조 카운터를 리턴합 니다 .
  • 29.
    shared_ptr: operator*(), operator->()구현 29 reference operator*() const // never throws { return *px; } T* operator->() const // never throws { return px; } private: T* px; int* pn; };//template<class T> class shared_ptr  간접지정 연산자와 화살표 연산자가 제대 로 동작하도록 함수를 작성합니다 .  void* 에 대한 참조가 동작하도록 하기 위해 서는 void 에 대한 참조를 템플릿 특화로 미 리 구현해 두어야 합니다 .
  • 30.
    main() 에서 테스트 30 intmain() { typedef shared_ptr<int> IntPtr; IntPtr spInt = new int(3); // spInt.use_count() == 1 if( spInt != NULL ) { std::cout << *spInt << std::endl; // 3 }//if IntPtr spInt2 = spInt; // spInt.use_count() == 2 IntPtr spInt3 = spInt; // spInt.use_count() == 3 spInt.reset( new int(4) ); // spInt.use_count() == 1 *spInt = 5; // 3 changed to 5 if( spInt2 != NULL ) // spInt2.use_count() == 2 { std::cout << *spInt2 << std::endl; // 3 }//if return 0; }//int main()
  • 31.
    copy-and-swap: swap() 메서드구현 31 void swap( shared_ptr<T>& right_ ) // never throws { std::swap( px, right_.px ); std::swap( pn, right_.pn ); }  exception safety 1) Basic: component 의 invariant 는 보존되고 , resource leak 은 발생하지 않아야 합니다 . 2) Strong: 성공적으로 완료되었던지 예외를 던졌던 지 둘 중의 하나여야 합니다 . 3) No-throw: 예외를 던지지 않아야 합니다 .  copy-and-swap 을 지원하기 위해서 shared_ptr 의 swap() 은 예외를 던지지 않도록 구현해야 합니다 .
  • 32.
    shared_ptr: swap() 을반영한 수정 32 shared_ptr& operator=( const shared_ptr& right_ ) { //release(); //px = right_.px; //pn = right_.pn; //if( px != NULL ) // *pn += 1; this_type(right_).swap(*this); return *this; } void reset(T * p) { //release(); //px = p; //pn = NULL; //if( px != NULL ) // pn = new int(1); this_type(p).swap(*this); }
  • 33.
    관찰 : if의 조건문 표현식 33 class KBoolTest { public: operator bool() const { return true; } operator int() const { return 1; } operator int*() const { return NULL; } int GetValue() const { return 9; } }; int main() { KBoolTest t; if( t ) std::cout << t.GetValue() << std::endl; return 0; }//int main()  if 문의 조건은 일반적인 bool 표현식이 아닌 C 가 허용하는 참인 조건식을 적을 수 있습니다 .  평가의 partial order 의 순서는 bool  native type  pointer 의 순입니다 .
  • 34.
    safe bool idiom 34 //int*p = spInt; // (1) //if( spInt < spInt ) // (2) //{ //}  지금까지의 구현은 (1) 과 (2) 처럼 잘못된 사 용이나 , 의미없는 bool 표현식을 막지 못 합니다 .   template <typename T> void some_func(const T& t) {     if (t)          t->print();   }  smart pointer T 에 대해 이 문장이 동작 하려면 T 는 bool 형에 대한 형 변환 연 산자를 제공해야 합니다 .
  • 35.
    해결시도 1: operatorbool() 구현 35 class Testable {     bool ok_;   public:     explicit Testable(bool b=true):ok_(b) {}       operator bool() const {       return ok_;     }   };  shared_ptr 은 자신이 valid 한 raw pointer 를 가질 때 , true 를 리턴하는 operator bool() 을 작성할 수 있습니다 .
  • 36.
    operator bool() cont. 36 test<< 1; // (1)   int i=test; // (2)   Testable a;   AnotherTestable b;     if (a==b) { // (3)   }     if (a<b) { // (4)   }  이 구현은 (1), (2) 같은 의미 없는 문장 이나 , (3), (4) 같은 의미 없는 bool 검 사를 막지 못합니다 .
  • 37.
    해결시도 2: operatorvoid*() 구현 37  operator void*() const {     return ok_==true ? this : 0;   }  이 구현은 영리해 보이지만 , 아래의 문장처럼 delete 를 직접호출 할 수 있는 약점을 가집니다 . Testable test; delete test;
  • 38.
    해결시도 3: nestedclass 38 class Testable {     bool ok_;   public:     explicit Testable(bool b=true):ok_(b) {}       class nested_class;       operator const nested_class*() const {       return ok_ ? reinterpret_cast<const nested_class*>(this) : 0;     }   }; Testable b1,b2;     if (b1==b2) {   }     if (b1<b2) {   }  safe bool 을 위한 nested class 구현 역시 의미없는 bool 검 사를 막지 못합니다 .  이 문제를 해결하려면 포인터 형변환이 되지만 , < 같은 비교 연산자를 지원하지 않는 데이터 타입을 사용하는 것입니다 .  흥미롭게도 멤버 함수에 대한 포인터가 그렇게 동작합 니다 !
  • 39.
    최종 버전 :safe bool idiom 39 class Testable {     bool ok_;     typedef void (Testable::*bool_type)() const;     void this_type_does_not_support_comparisons() const {}   public:     explicit Testable(bool b=true):ok_(b) {}       operator bool_type() const {       return ok_==true ?         &Testable::this_type_does_not_support_comparisons : 0;     }   };  if- 문장의 조건 표현식에 포인터가 사용될 수 있는 점을 이용하여 , 함수 포인터를 리턴하 는 연산자 함수를 정의합니다 .
  • 40.
    shared_ptr 의 내부: unspecified_bool_type 사용 40 //operator pointer() const //{ // return px; //} void unspecified_bool() const { } typedef void (shared_ptr::*unspecified_bool_type)() const; operator unspecified_bool_type() const // never throws { return px == 0 ? 0 : &shared_ptr::unspecified_bool; }  boost 의 실제 코드는 unspecified_bool_type() 을 사용합니다 .  C++ 표준 라이브러리 뿐만 아니라 , 게임 엔진의 스마트 포인터 구현은 모두 이러한 기법을 사용합니다 .
  • 41.
    shared_ptr 에 적용된safe bool idiom 41 typedef shared_ptr<int> IntPtr; IntPtr spInt = new int(3); IntPtr spInt2 = spInt; IntPtr spInt3 = spInt; spInt.reset( new int(4) ); *spInt = 5; if( spInt2 != NULL ) { std::cout << *spInt << std::endl; }//if int* p = spInt; // (1) error if( spInt2 < spInt3 ) // (2) error { }  이제 (1) 과 (2) 같은 의미 없는 문 장은 컴파일 시간 에러가 발생합 니다 .
  • 42.
    implicit constructor 문제해결하기 42 void Test( shared_ptr<int> spInt_ ) { } int iData = 5; Test( &iData );  실제 포인터가 아닌 , 포인터 표현식에 대한 shared_ptr 생성을 막을 필요가 있습니다 .  이 문제에 대한 해결방법은 명시적 생성만 가능하도록 클래스를 설계하는 것입니다 . explicit shared_ptr(T * p = 0) : px(p), pn(0) { if( px != NULL ) pn = new int(1); }
  • 43.
  • 44.
    weak_ptr<T> 44 #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> classTest; typedef boost::shared_ptr<Test> TestPtr; typedef boost::weak_ptr<Test> TestWeakPtr; class Test { public:  shared_ptr 을 제공할 때 , 포인터 순환 참 조 문제등을 해결하기 위해서 weak_ptr 을 함께 제공합니다 .
  • 45.
    weak_ptr<T> cont. 45 class TestRef { public: TestRef(){} voidSetTest( TestPtr tp ) { m_tp = tp; } void Print() const { if( TestPtr tp = m_tp.lock() ) { tp->Print(); } } private: TestWeakPtr m_tp; };  weak_ptr 을 사용하는 경우 , 지금 사용해 도 안전한 실제 포인터를 얻기 위해서 lock() 을 호출합니다 . int main() { TestPtr tp = Test::CreateTest( 1 ); TestRef ref; ref.SetTest( tp ); tp.reset(); ref.Print(); }//int main()
  • 46.
    weak_ptr<T> lock() 46  weak_ptr<T>의 lock() 구현 shared_ptr<T, A, D> lock() const // never throws { // optimization: avoid throw overhead if(expired()){ return shared_ptr<element_type, A, D>(); } BOOST_TRY{ return shared_ptr<element_type, A, D>(*this); } }  shared_ptr<T>( weak_ptr<Y> ) 구현 template<class Y> explicit shared_ptr(weak_ptr<Y> const & r): pn(r.pn) // may throw { // it is now safe to copy r.px, as pn(r.pn) did not throw px = r.px; }
  • 47.
    raw pointer 에서shared_ptr 얻기 : factory 47 class X { private: X() { ... } public: static shared_ptr<X> create() { shared_ptr<X> px(new X); // use px as 'this_' return px; } };  shared_ptr<T> 로 관리되지 않는 raw 포 인터에서 shared_ptr<T> 을 얻을 필요가 있습니다 .  클래스 객체가 shared_ptr<T> 로 관리되 더라도 , 생성자에서 shared_ptr<T> 를 얻 을 방법도 없습니다 .  해결 방법은 weak_ptr 과 팩토리 함수를 사용하는 것입니다 .
  • 48.
    factory: this 에대한 shared_ptr<T> 48 class impl: public X, public Y { private: weak_ptr<impl> weak_this; impl(impl const &); impl & operator=(impl const &); impl() { ... } public: static shared_ptr<impl> create() { shared_ptr<impl> pi(new impl); pi->weak_this = pi; // (1) 팩토리에서 weak_ptr<T> 을 초기화 해 놓습니다 . return pi; } virtual void f() { ... } virtual shared_ptr<X> getX() { shared_ptr<X> px(weak_this); return px; } };  대상 객체를 생성할 때 , weak_ptr 이 적절 하게 초기화되도록 합니다 .  그러면 , 후에 필요하면 대상 객체에 대한 shared_ptr 을 얻을 수 있습니다 .
  • 49.
    boost::enable_shared_from_this<T> 49 #include <iostream> #include <boost/shared_ptr.hpp> #include<boost/enable_shared_from_this.hpp> class Test; typedef boost::shared_ptr<Test> TestPtr; class Test : public boost::enable_shared_from_this<Test> { public: static TestPtr CreateTest(int i) { TestPtr sp( new Test( i ) ); return sp; } Test(int i) : m_i(i) {} void Print() const { std::cout << m_i << std::endl; } private: int m_i; };  이 역할을 하는 boost 의 베이스 클래스가 enabled_shared_from_this 입니다 .
  • 50.
    shared_from_this() 50 void TestFun( Test*pt ) { TestPtr tp; if( pt != NULL ) tp = pt->shared_from_this(); if( tp != NULL ) { tp->Print(); } }  Test 가 enable_shared_from_this 를 상속받은 경ㅇ 우 , raw pointer 에서 shared_ptr 을 얻기 위해서는 shared_from_this() 를 호출합니다 .
  • 51.
    응용 예 :RAII 51 #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/typeof/typeof.hpp> #define KSmartPointer(type_) class type_; typedef boost::shared_ptr<type_> type_##SharedPtr; typedef boost::weak_ptr<type_> type_##WeakPtr; #define IF_MEMBER_WEAKPTR( member_name_ ) if( boost::shared_ptr<BOOST_TYPEOF(member_name_)::element_type> member_name_ = this- >member_name_.lock() )  게임 객체가 관리되는 리소스가 있는 경우에만 사용하려 고 하는 경우 , 리소스에 대한 weak_ptr 을 멤버로 가집니 다 .  게임 객체가 실제 객체를 사용하려고 하면 , lock() 호출이 필요합니다 .  이러한 패턴을 RAII 매크로로 작성했습니다 .
  • 52.
    응용 예 cont. 52 classTestContainer { public: TestContainer(){} void SetTest( TestSharedPtr tp ) { m_tp = tp; } void Print() const { IF_MEMBER_WEAKPTR( m_tp ) { m_tp->Print(); }//if } private: TestWeakPtr m_tp; };  IF_MEMBER_WEAKPTR() 은 RAII 스타일의 매크 로로 멤버에 대한 안전한 접근을 보장합니다 .
  • 53.
  • 54.

Editor's Notes

  • #19 BOOST_TYPEOF()를 사용하면, 매크로 인자만 사용하여 해당하는 iterator타입을 접근하는 코드를 작성하는 것이 가능합니다.