http://jacking.tistory.com/590


괴로운 메모리 관리 - shared_ptr / weak_ptr

 

C/C++ 에서 메모리 관리는 프로그래머에게 맡겨져 있습니다귀찮은 것으로 프로그래머의 약간의 실수가 큰 사고로 연결 되는 경우가 허다합니다.

  

 

포인터는 귀찮다

 

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

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

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

delete p;

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

 

 

 

std::auto_ptr의 한계

 

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

    

auto_ptr 이라면

#include

#include ring>

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

 

using namespace std;

 

class item {

private :

string value_;

public :

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

 {

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

  }

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

  string value() const { return value_; }

};

 

int main() 

{

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

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

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

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

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

}

 

실행 결과 >

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 에서는 인스턴스를 공유할 수 없다

class person {

string name_;

public :

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

auto_ptr child; // 자식

};

 

person sazae( "소라" );

person masuo( "마스오" );

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

// "타라" "소라" "마스오"의 자식로 하고 싶지만

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

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

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

#include <iostream>

#include <string>

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

 

using namespace std;

 

class item {

private :

string value_;

public :

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

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

 }

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

string value() const { return value_; }

};

 

int main() 

{

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

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

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

   {

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

      tr1::shared_ptr<item> p2 = p1;

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

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

   }

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

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

}


< 실행 결과 > 

item(something) ctor

p1->value() = something

p2->value() = something

p1->value() = something

item(something) dtor


std::tr1:weak_ptr 약 참조 포인터 shared_ptr 을 사용하는 것에 의해서 번잡한 메모리 관리 로부터 해방됩니다만 이것으로도 아직 순환 참조 라고 하는 문제가 남아 있습니다.

 


순환 참조란

#include <iostream>

#include <string>

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

 

class Person {

public :

string name; // 이름

tr1::shared_ptr spouse; // 배우자

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

void info() const {

cout << "My name is " << name

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

}

};

 

int main() 

{

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

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

   {

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

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

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

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

      one->info();

      two->info();

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

   }

   one->info();

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

}

 

실행 결과 >

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에 의한 해결

#include <iostream>

#include <string>

#include <boost/tr1/memory.hpp>

// std::tr1::shared_ptr, std::tr1::weak_ptr

 

class Person {

public :

string name;

tr1::weak_ptr<spouse> spouse;

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

void info() const {

cout << "My name is " << name

<< " and my spouse " ;

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

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

else

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

}

};

 

int main() 

{

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

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

   {

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

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

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

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

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

      one->info();

      two->info();

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

   }

   one->info();

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

}

 

실행 결과 >

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에는 그 외에도 편리한 클래스가 다수 수록되고 있습니다속편을 기대하세요.



원본 : http://codezine.jp/a/article/aid/1937.aspx

반응형

'메타프로그래밍 > Boost::' 카테고리의 다른 글

Boost::mpl  (0) 2012.12.25
boost::thread  (0) 2012.12.25
boost::shared_ptr  (0) 2012.11.18
boost::function  (0) 2012.11.13
boost::unordered_set  (0) 2012.11.13

+ Recent posts