티스토리 뷰
1. delete를 직접 하지말고 자원관리객체에게 맡겨라
T *p = new T;
위의 코드를 다음으로 바꿉니다.
std::tr1::shared_ptr<T> p( new T );
자원을 획득하자마자 자원관리 객체의 초기화 코드로 넘기는데 이를 자원획득즉초기화(RAII) 라 합니다.
2. delete[] 와 shared_ptr
shared_ptr 의 생성자는 타입T 의 포인터, 즉 T* 만을 받는 생성자가 있고 T* 와 삭제자(deleter) 를 받는 생성자도 있습니다.
삭제자를 넘겨주지 않는 첫 번째의 생성자같은 경우 shared_ptr 는 자신이 소멸할 때 자원에 delete 를 적용해줍니다. 근데 만약 아래처럼
std::tr1::shared_ptr<T> p( new T[10] );
동적 배열 자원을 넘겨주게 되면 new[] 로 할당된 자원을 delete[] 가 아닌 delete 로 반납을 행하는데 이런 경우오작동 또는 메모리 누수가 발생하게 됩니다. 이를 막기위해 두 가지 방식을 취할 수 있습니다.
첫 번째는 vector 와 같은 컨테이너로 여러 shared_ptr 객체를 이용하는 방법입니다.
std::vector< std::tr1::shared_ptr<T> > v( 10 );
v[0] = std::tr1::shared_ptr<T>( new T );
...
두 번째는 delete[] 를 이용하는 삭제자를 정의( 일종의 함수객체 ) 하고 shared_ptr 의 생성자로 넘겨주는 방법입니다.
struct arrayDeleter
{
template< typename T >
void operator()( T *p )
{
delete[] p;
}
};
std::tr1::shared_ptr<T> p( new T[10], arrayDeleter() );
이렇게 하면 정상적으로 자원의 할당 / 반납이 행해집니다.
세 번째는 shared_array 사용
3. 삭제자의 다양한 활용
int a = 0;
std::tr1::shared_ptr<int> p( &a );
어떤 변수를 포인터로 가리키는 경우는 자주 볼 수 있습니다. 그와 비슷하게 shared_ptr 로 지역변수 a 를 가리키도록 한 것이 위 의 코드입니다. 그러나, 객체 p가 소멸할 때 &a 에 delete 를 적용하기 때문에 결과적으로 delete &a; 동작이 소멸 과정에서 행해집니다. 여기서 a 는 동적 자원이 아닌 지역변수이기 때문에 에러가 발생하게 됩니다. 이를 막기위해 소멸과정에서 어떠한 동작도 행하지 않도록 빈 삭제자( Empty deleter ) 를 지정할 수 있습니다.
struct emptyDeleter
{
template< typename T >
void operator()( T *p ){}
};
int a = 0;
std::tr1::shared_ptr<int> p( &a, emptyDeleter() );
이 번에는 C버전 파일입출력 라이브러리에서 fclose() 함수 호출을 shared_ptr 를 이용하여 자동으로 처리되도록 하는 예입니다.
struct fileCloser
{
void operator( FILE *f )
{
if( f ){ fclose( f ); }
}
};
std::tr1::shared_ptr<FILE> f( fopen( "test.txt", "rb" ), fileCloser() );
4. shared_ptr<T> <-> T*
가끔 shared_ptr 객체가 관리하는 자원을 얻어내야 하는 경우가 있는데 그 땐 shared_ptr<T>::get() 멤법함수를 이용합니다.
std::tr1::shared_ptr<T> p( new T );
T *ptr = p.get(); // p.get() 은 T* 를 반환합니다.
5. 자원의 교체
shared_ptr 객체가 관리하고 있는 자원을 다른 자원 또는 NULL 로 만들어야 하는 경우는 shared_ptr<T>::reset() 멤버함수를 이용합니다.
std::tr1::shared_ptr<T> p( new T );
p.reset(); // 이제 p는 자원을 관리하지 않습니다. 기존의 자원은 안전하게 delete 됩니다.
p.reset( new T ); // p는 새로운 자원을 관리합니다.
p = std::tr1::shared_ptr<T>(); // 빈 shared_ptr 객체를 대입합니다. p.reset(); 과 동일합니다.
6. 복사와 참조 카운팅
shared_ptr 객체는 복사될 때마다 자원을 복사하는게 아닌 자원의 참조개수를 증가시킵니다.
std::tr1::shared_ptr<int> p( new int ); // 자원은 p에 의해 1번 참조됩니다.
std::tr1::shared_ptr<int> p2( p ); // 자원은 p, p2에 의해 2번 참조됩니다.
p.reset(); // 자원은 p2에 의해 1번 참조됩니다.
p = p2; // 자원은 p, p2에 의해 2번 참조됩니다.
p2.reset(); // 자원은 p에 의해 1번 참조됩니다.
p.reset(); // 자원은 더 이상 참조되지 않음으로 delete 됩니다.
7. 조건문과 shared_ptr
기존 포인터를 이용할 때 유효한 포인터인지 확인하기 위해
int *p = &a;
...
if( p != NULL )
{ ... }
위의 코드처럼 if 를 이용하여 NULL 과 비교하곤 했습니다. 이를 shared_ptr 에서는 다음처럼 작성합니다.
std::tr1::shared_ptr<int> p( new int );
...
if( !p )
{ ... }
위에서 !p 처럼 이용하면 됩니다. 즉 shared_ptr 객체는 bool 식으로 변환될 수 있습니다.
8. const 자원과 shared_ptr
상수 자원을 관리하거나 자원을 상수화시키기 위해 const 를 이용할 수 있습니다.
std::tr1::shared_ptr<int> p( new int ); // p객체는 언제든지 자원을 변경할 수 있습니다.
std::tr1::shared_ptr<int const> q( new int ); // q객체는 자원을 읽을수는 있으나 변경할 수는 없습니다.
p = q; // 자원을 변경하지 못하는 q객체를 자원이 변경가능한 p에 복사합니다.
// 그러나 이 코드는 컴파일되지 않습니다.
q = p; // 자원이 변경가능한 p객체를 자원을 변경하지 못하는 q객체에 복사합니다.
// 컴파일이 잘 됩니다.
기존 포인터에서 적용되던 상수성을 그대로 유지해줍니다.
9. 상속과 shared_ptr
Child 클래스가 Parent 클래스를 public 상속했다고 할 때 기존 포인터를 이용하여
Parent *p = new Child;
위와 같이 작성할 수 있습니다. 이를 shared_ptr 로 작성하면 다음과 같습니다.
std::tr1::shared_ptr<Parent> p( new Child );
다형성을 이용하는 곳에도 별다른 지장없이 shared_ptr 를 이용할 수 있습니다.
10. typedef 활용
shared_ptr 를 이용하면 타입이름이 너무 길어지게 되는데 여기에 typedef 를 활용합니다.
class CMyClass;
typedef std::tr1::shared_ptr<CMyClass> CMyClass_ptr;
class CMyClass
{
...
CMyClass_ptr getThis(){ return CMyClass_ptr( this, emptyDeleter() ); }
...
};
위처럼 std::tr1::shared_ptr<T> 타입을 T_ptr 로 typedef 하는 방법입니다.
this 포인터를 넘기는 또다른 방법
boost::enable_shared_from_this 를 사용(http://naiades.tistory.com/tag/enable_shared_from_this)
11. 순환참조 문제
사람 클래스가 있고 멤버로 친구가 있다고 하겠습니다.
struct person
{
std::string name;
std::tr1::shared_ptr<person> myFriend;
person( const std::string &_name ) : name( _name )
{
std::cout << name << "태어나다." << std::endl;
}
~person()
{
std::cout << name << "죽다." << std::endl;
}
};
그리고 아래와 같은 블럭이 실행되면 생성자는 제대로 호출되지만 소멸자가 호출되지 않는 문제가 발생합니다.
{
std::tr1::shared_ptr<person> kim( new person( "kim" ) );
std::tr1::shared_ptr<person> lee( new person( "lee" ) );
kim->myFriend = lee;
lee->myFriend = kim;
}
원인을 알아보겠습니다. 블럭의 끝에서 lee 객체가 먼저 소멸하는데 참조개수가 둘( 자신과 kim의 친구로) 이므로 참조개수를 하나 줄이고 소멸은 끝납니다. 즉 lee 의 참조개수는 1이고 kim의 참조개수는 2입니다.
다음 kim 객체가 소멸하는데 마찬가지로 참조개수가 둘( 자신과 lee의 친구로 ) 이므로 참조개수를 하나 줄이고 소멸이 끝납니다. 결과적으로.. kim, lee 둘 다 참조개수가 1인 상태로 남아있게 되는 문제가 발생했습니다.
이렇게 다른 객체끼리 서로 참조를 갖는 형태를 순환참조라 하며 shared_ptr 는 순환참조시 제대로 자원해제를 하지 못합니다.
이런 경우는 친구를 shared_ptr<person> 타입이 아닌 person* 타입으로 바꾸거나 빈 삭제자를 이용하거나 또 다른 스마트포인터중 하나인 weak_ptr 를 이용하여 해결할 수 있습니다. person* 타입으로 바꾸는 것은 쉽기 때문에 생략하고
먼저 빈 삭제자를 이용하는 방법을 말씀드리겠습니다.
아 래의 코드 대신
kim->myFriend = lee;
lee->myFriend = kim;
다음의 코드를 이용합니다.
lee->myFriend.reset( kim.get(), emptyDeleter() );
kim->myFriend.reset( lee.get(), emptyDeleter() );
이제 정상적으로 소멸되는 것을 확인할 수 있습니다.(두번 삭제 방지)
이 번엔 weak_ptr 을 이용해보겠습니다. 그 전에 weak_ptr 의 특징은 shared_ptr 객체를 받을 수 있으며 자원의 참조개수에 변화를 주지 않습니다. 그렇기 때문에 이런 순환문제에도 적절하게 동작하게 됩니다.
person 의 멤버변수를 아래의 코드 대신
std::tr1::shared_ptr<person> myFriend;
다음의 코드를 이용합니다.
std::tr1::weak_ptr<person> myFriend;
나머지 코드는 동일합니다. 마찬가지로 정상적으로 소멸되는 것을 확인할 수 있습니다.
- Total
- Today
- Yesterday
- distribution
- SDK
- objective-c
- setSelectionIndicatorImage
- badgeValue
- iOS5
- UITableView
- AVAudioSessionInterruptionNotification
- endInterruption
- CAD
- git hub
- xcode
- object-c
- apns
- MFC
- ios
- iPhone
- ManagedObjectModel
- C++
- beginInterruption
- AVAudioSession
- C
- ARX
- AVAudioSessionDelegate
- progressively
- UINavigationBar
- 배포
- 애플 문서
- MappingModel
- setBackGroundImage
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |