본문 바로가기

프로그래밍

[펌] Boost 로 C++0x의 라이브러리 「TR1」을 미리 사용 해 보자 (1)

[중급] array  shared_ptr/weak_ptr


 

C++의 새로운 규격 「C++0x」에 추가 예정 라이브러리 「TR1」의 개요를 Boost판을 사용 하여 해설합니다(Visual Studio 2008에도 추가 패키지로서 공급될 예정).

제1회는 array  shared_ptr/weak_ptr에 대해서 .

 

서두서두
 C++의 새로운 규격 「C++0x」에서는 언어와 라이브러리 양면으로부터 편리한 기능이 추가됩니다. 「TR1」(Technical Report 1)은 C++0x의 라이브러리 부에서 표준 C++에 새롭게 추가되는 라이브러리의 상당수는 Boost 중에서 선택된 것입니다. 2008년 봄에 릴리스가 예정되어 있 Visual Studio 2008에도 추가 패키지로서 공급된다라는 정보를 얻고 있습니다.
 TR1에 수록된 클래스/함수 중에서 몇 개인가를 픽업, 그 개요와 사용법을 예습해 둡시다.


전 준비 - Boost의 인스톨

Boost의 인스톨은 개발 환경이 Visual Studio 2005이면 아주 간단합니다. Boost consulting - Free Downloads 에 있는 인스톨러를 다운로드/실행하는 것만으로 필요한 헤더, 라이브러리 및 문서가 인스톨 됩니다.
인스톨이 완료되면 Visual Studio 2005의 옵션을 변경 하여 Boost의 인클루드/라이브러리 패스를 설정해 둡시다. 

인클루드 패스

 

 

 

 

 

 

 

 

 

 

 


라이브러리 패스

 

 

 

 

 

 

 

 

 

 

 

 

 

std::tr1::array - 고정

우선은 간단한 것으로부터. 프로그램 중에서 가장 많이 사용하고 있는 것이 배열입니다. 언제나 사용하고 있는 배열 int data[5] 의 종류를 클래스로서 제공하는 것입니다.


     배열을 사용한 간단한 샘플


  1. #include <iostream>
    #include <algorithm>
    #include <iterator>
    using namespace std;int main()
  2. {
          const size_t N = 5;
          int arr[N] = {1,2,3,4,5};
          // 배열의 요소를 출력 한다 
       copy(arr, arr+N, ostream_iterator< int >(cout, " " ));
       cout << endl;
    }

배열에 대해 표준 C++의 <algorithm>을 적용할 때 적용 범위 즉 반복자 묶음으로서 배열의 선두와 말미를 이 예에서는 arr arr+N 을 줍니다.

      표준 C++ 라이브러리의 컨테이너

  1. std::vector 라면…vector< int > ivec;
    // 배열의 요소를 출력 한다 
    copy(ivec.begin(), ivec.end(), ostream_iterator< int >(cout, " " ));

와 같이 반복자를 돌려주는 멤버 begin() /end() 사용할 수 있습니다. 고정 배열 array는 표준 C++ 라이브러리의 컨테이너와 같은 멤버를 가지면서 그 실체는 빌트인 배열이라고 하는 품격이 틀린 클래스입니다.
      boost::array 라면...


  1. #include  <iostream>
    #include <algorithm>
    #include <iterator>
    #include < boost/tr1/array.hpp > // std::tr1::array 
    using namespace std;int main() {
          // template인수에는 요소의 형태와 요소 수를 지정한다 
       tr1:: array< int ,5> arr = {{ 1,2,3,4,5}}; // {{ 와 }}  둘러쌀 것 
       copy(arr.begin(), arr.end(), ostream_iterator< int >(cout, " " ));
    }

 std::tr1::array  template인수 T,N에는 각각 요소의 형태와 요소 수를 지정합니다. 각 요소의 초기화가 필요하면 요소 줄을 {{  }} 둘러싸 주세요.


std::tr1::array 의 주요한 함수군을 아래 에 나타냅니다.


size() 
요소 수 N을 돌려줍니다.

operator[](n) , at(n) 
n번째의 요소. at(n) 은 범위 외( n >= N ) 때 std::range_error 예외를 throw합니다.

front() , back() 
각각 선두 요소/말미 요소를 돌려줍니다.

begin() , end() 
각각 선두/말미의 반복자를 돌려줍니다.

rbegin() , rend() 
각각 선두/말미의 역순 반복자를 돌려줍니다. 역순으로 프린트

  1. tr1::array< int ,5> arr = {{1,2,3,4,5}};
    copy(arr. rbegin() , arr. rend() , ostream_iterator< int >(cout, " " ));

 
swap(other) 
array other 와의 사이에 각 요소를 교환합니다. X 와 y를 통째로 교환

  1. tr1::array< int ,5> x;
    tr1::array< int ,5> y;x.swap(y) ;

 
assign(value) 
 요소에 value 를 대입합니다.  모든 요소를 -1  초기화

  1. tr1::array< int ,5> x;x.assign(-1) ;

 == , != , <<span style="font-family:Times New Roman; font-size:10pt;"> , <= , >, >= 
 array 를 비교합니다. 대소 관계는 사전순서(lexicographical)에 근거합니다.

 

 

 

로운 메모리 관리 - shared_ptr / weak_ptr

C/C++ 에서 메모리 관리는 프로그래머에게 겨져 있습니다. 귀찮은 것으로 프로그래머의 약간의 미스가 대 사고 연결 되는 것이 고민의 씨앗입니다 .

      포인터는 귀찮다…

  1. string* p = new string( "one" );

  2. string* q = new string( "two" );

  3. p = q; // p,q 모두 "two" 를 가리킨다. "one"은 미아 로.

  4. delete p;

  5. delete q; // "two"를 다중delete!

 

std::auto_ptr의 한계

표준 C++라이브러리는 메모리 관리 를 편하게 하는 std::auto_ptr 를 제공하고 있습니다.

     auto_ptr 이라면

  1. #include 

  2. #include ring>

  3. #include <memory> // std::auto_ptr<T> 

  4.  

  5. using namespace std;

  6.  

  7. class item {

  8. private :

  9. string value_;

  10. public :

  11. item( const char * v= "???" ) : value_(v) {

  12. cout << "item(" << value_ << ") ctor\n" ;

    }

  13. ~item() { cout << "item(" << value_ << ") dtor\n" ; }

  14. string value() const { return value_; }

  15. };

  16.  

  17. int main() {

  18. auto_ptr<item> p( new item( "one" ));

  19. auto_ptr<item> q( new item( "two" ));

  20. p = q; // p는 가리키고 있었  "one"을 delete해, "two"를 가리킨다. q는 null  된다. 

  21. cout << "p points to " << (p.get() ? p->value() : "(null)" ) << endl;

  22. cout << "q points to " << (q.get() ? q->value() : "(null)" ) << endl;

  23. }

실행 결과

item(one) ctor

item(two) ctor

item(one) dtor

p p oints to two

q points to (null)

item(two) dtor

 

실행 결과가 나타내 보이는 대로 constructor  소멸자의 수가 일치하고 있을 테니 delete 잊 이나 다중 delete 발생하고 있지 않는 것을 압니다. auto_ptr  소멸에 그것이 가리키는 포인터를 delete 하므로 명시적으로 delete 할 필요가 없습니다.

다만  auto_ptr 사이의 복사(대입)를 하면 포인터의 소유권(=삭제 책임)이 이동 하여 복사 처(source)에서는 포인터가 없어집니다(실행 결과의 q "two"를 가리키고 있지 않아요). 그 때문에 복수의 auto_ptr 하나의 인스턴스를 가리켜 공유할 수 없습니다.

     auto_ptr 에서는 인스턴스를 공유할 수 없다

  1. class person {

  2. string name_;

  3. public :

  4. person(string n) : namae_(n) {}

  5. auto_ptr child; // 아이 

  6. };


  7. person sazae( "소라" );

  8. person masuo( "마스오" );

  9. auto_ptr tara( new person( "타라" ));

  10. // "타라"를 "소라"와 "마스오"의 아이로 하고 싶지만 

  11. sazae.child = tara; // 이 순간"타라"의 소유권이tara로부터 

  12. // sazae.child로 이동(양도된다) 

  13. masuo.child = tara; // masuo.child  "타라"를 가리킬 수 없다

 

 new된 인스턴스를 자동적으로 delete  주는 auto_ptr 은 편리 한 것은 틀림 없습니다만 이 제한이 있기 때문에 용도가 한정되어 버립니다.

 

std::tr1::shared_ptr - 공유 포인터

TR1 에서 새롭게 추가된 shared_ptr 은 참조 카운트라고 하는 것으로 인스턴스를 관리합니다. shared_ptr 은 그 내부에그 인스턴스를 참조하고 있 shared_ptr의 총수를 보관 유지하고 있습니다. shared_ptr 의 소멸자는 내부의 참조수를 -1하여 그것이 0이 되었을 때 인스턴스가 어디에서도 참조되지 않게 되었다 라고 판단 하고 인스턴스를 delete 합니다.

      shared_ptr

  1. #include <iostream>

  2. #include <string>

  3. #include <boost/tr1/memory.hpp> // std::tr1::shared_ptr 

  4.  

  5. using namespace std;

  6.  

  7. class item {

  8. private :

  9. string value_;

  10. public :

  11. item( const char * v= "???" ) : value_(v) {

  12. cout << "item(" << value_ << ") ctor\n" ;

     }

  13. ~item() { cout << "item(" << value_ << ") dtor\n" ; }

  14. string value() const { return value_; }

  15. };

  16.  

  17. int main() {

  18. // p1  "something"을 가리킨다. 참조수:1 

  19. tr1::shared_ptr<item> p1( new item( "something" ));

  20. cout << "p1->value() = " << p1->value() << endl;

  21. {

  22. // p2도 "something"을 가리킨다. 참조수:2 

  23. tr1::shared_ptr<item> p2 = p1;

  24. cout << "p2->value() = " << p2->value() << endl;

  25. // 여기서p2 사라진다. 참조 회수:1 

  26. }

  27. cout << "p1->value() = " << p1->value() << endl;

  28. // 여기서p1 사라진다. 참조수:0되어 "something"  delete된다 

  29. }


실행 결과item(something) ctorp1->value() = somethingp2->value() = somethingp1->value() = somethingitem(something) dtorstd::tr1:weak_ptr 약 참조 포인터 shared_ptr 을 사용하는 것에 의해서 번잡한 메모리관리 로부터 해방됩니다만 이것이라도 아직 순환 참조 고 하는 문제가 남아 있습니다.

     순환 참조란

  1. #include <iostream>

  2. #include <string>

  3. #include <boost/tr1/memory.hpp> // std::tr1::shared_ptr 

  4.  

  5. class Person {

  6. public :

  7. string name; // 이름 

  8. tr1::shared_ptr spouse; // 배우자 

  9. Person(string n) : name(n) {}

  10. void info() const {

  11. cout << "My name is " << name

  12. << " and my spouse is " << spouse->name << endl;

  13. }

  14. };

  15.  

  16. int main() {

  17. // one  "adam"을 가리킨다. 참조수:1 

  18. tr1::shared_ptr<Person> one( new Person( "adam" ));

  19. {

  20. // two  "eve"을 가리킨다: 참조수:1 

  21. tr1::shared_ptr<Person> two( new Person( "eve" ));

  22. one->spouse = two; // "adam"의 아내는"eve" 참조수:2 

  23. two->spouse = one; // "eve"의 남편은"adam" 참조수:2 

  24. one->info();

  25. two->info();

  26. // 여기서two 사라진다. 참조수:1 ... 0은 아니기 때문에 "eve"  delete 되지 않는다 

  27. }

  28. one->info();

  29. // 여기one 사라진다. 참조수:1 ... 0은 아니기 때문에 "adam" delete 되지 않는다 

  30. }


실행 결과

My name is adam and my spouse is eve

My name is eve and my spouse is adam

My name is adam and my spouse is eve

 

이 예 와 비슷하게 복수의 shared_ptr  서로를 서로 참조해 루프를 형성하(순환 하는 ) 참조수가 0이 되는 것이 없기 때문에 인스턴스가 delete 되지 않고 남아 버립니다.

이 문제를 해소하기 위해 TR1은 한층 더 하나 더 weak_ptr 을 제공합니다. weak_ptr  shared_ptr 보관 유지하는 참조수의 증감에 관여하지 않습니다. 또한 weak_ptr 의 멤버 expired() 에 의해서 인스턴스의 소실을 알 수 있습니다.

     weak_ptr에 의한 해결

  1. #include <iostream>

  2. #include <string>

  3. #include <boost/tr1/memory.hpp>

  4. // std::tr1::shared_ptr, std::tr1::weak_ptr 

  5.  

  6. class Person {

  7. public :

  8. string name;

  9. tr1::weak_ptr<spouse> spouse;

  10. Person(string n) : name(n) {}

  11. void info() const {

  12. cout << "My name is " << name

  13. << " and my spouse " ;

  14. if ( spouse.expired() ) // 인스턴스의 유무를 판단한다 

  15. cout << "has gone...\n" ;

  16. else 

  17. cout << spouse.lock()->name << endl;

  18. }

  19. };

  20.  

  21. int main() {

  22. // one  "adam"을 가리킨다. 참조수:1 

  23. tr1::shared_ptr<Person2> one( new Person2( "adam" ));

  24. {

  25. // two  "eve"를 가리킨다: 참조수:1 

  26. tr1::shared_ptr<Person2> two( new Person2( "eve" ));

  27. // weak_ptr은 참조수의 증감에 관여하지 않는다 

  28. one->spouse = two; // "adam"의 아내는 "eve" 참조수:1 

  29. two->spouse = one; // "eve"의 남편은 "adam" 참조수:1 

  30. one->info();

  31. two->info();

  32. // 여기서 two 사라진다. 참조수:0  되어 "eve"  delete 된다 

  33. }

  34. one->info();

  35. // 여기 one 사라진다. 참조수:0  되어 "adam"  delete 된다 

  36. }


실행 결과

My name is adam and my spouse eve

My name is eve and my spouse adam

My name is adam and my spouse has gone...

정리

차기 C++규격 「C++0x」 에서 확장 예정의 라이브러리 「TR1」로부터 array  shared_ptr /weak_ptr 를 소개했습니다.이것들을 활용하는 것으로 C++에 대해 귀찮은 메모리 관리가 훨씬 편해지겠지요.

TR1에는 그 외에도 편리한 클래스가 다수 수록되고 있습니다. 속편을 기대하세요.

 

**  Boost 라이브러리의 1.34와 1.35 간에 _ptr 사용시 다른 점이 있습니다. 탬플릿 인자를 넣어줘야 되더군요.

     shared_ptr<T> p(new Y); 식으로 말이죠. 위의 예문을 1.35 버전을 근거로 수정합니다. **

 

 

번역 후기........

번역된 글을 퍼 갈 때는 꼭 아래의 글을 같이 복사 해 주세요.

번역은 원 저자에게 허락을 받은 것은 아니므로 상업적으로 사용할 경우에는 꼭 원저자에게 허락을 받기를 바랍니다.

출처 : http://codezine.jp/a/article/aid/1937.aspx

번역 :   choi_2.jpg   최흥배 ( jacking75@gmail.com ). 

             (주) 다이슨 파이퍼스튜디오에서 서버 프로그래머로 근무 중.