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