SAFE_DELETE(-)의 장점이 객체를 두 번 해제하지 않는다는 것입니다만
SAFE_NEW(-)가 있다면 객체를 분실하지 않게 만드는 장점이 있을 것 같은 데 왜 그런 매크로는 없을까요?
이 매크로의 문제점을 좀 알려주세요.
SAFE_NEW(pObject, Object) { assert(pObject == NULL ); if (pObject == NULL) { pObject = new Object(); }
연의 비유 :-)
객체를 연이라하면 그 객체를 가리키고 있는 포인터를 실타래라 할 수 있다.
실타래에 연이 묶여있으면 새로운 연을 붙들어 매지 않게하고 그러한 시도를 알리며 연이 묶여 있지 않을 때만 새로운 연을 붙들어 매는 게 SAFE_NEW의 역할이다.
SAFE_NEW 코드가 어째 디자인 패턴의 싱글톤 같네요.
딱 한 개를 만들어서 관리하는 목적인데, 생각 외로 쓸 때가 많네요.
전역적으로 쓰일 수 있으니깐, 전역 변수대신 쓰고 있어요.
class singleton
{
public
static singleton* getInstance();
private:
singleton() {};
~singleton() {};
singleton* m_pObject;
};
singleton* getInstance()
{
if( m_pObject == NULL )
{
m_pObject = new singleton();
}
return m_pObject;
}
같은 패턴으로 생각해 보자면 singleton의 전형적인 구현 패턴을 보면 getInstance()를 호출하여 객체를 생성하게 되어 있는 데
왜 deleteInstance() 같은 메쏘드가 내장된 경우를 보기 힘들까요? singleton::deleteInstance()는 SAFE_DELETE()와 비슷해야죠.^^
singleton::deleteInstance()에 문제가 없을거라고 봅니다.
SAFE_DELETE()는 SAFE_NEW()가 없고 singleton::getInstance()는 singleton::deleteInstance()가 없습니다.
저는 대칭성 없음을 지적하고 싶네요.^^
SAFE_DELETE()는 태생적으로 대칭성과는 무관한 녀석이 아닌가요?
NULL 포인터를 해제 해버리겠다는 행동을 막는건대말이죠.
반대로 포인터가 객체를 가리키고 있을 때 그 객체로의 연결을 끊어버리지 않기 위해서 SAFE_NEW()를 통해 객체를 생성하고 그 주소를 포인터에 대입하면 어떠냐는 이야기입니다.
반대라기 보다 상대적인 개념이라고 할 수 있죠.
그리고 SAFE_DELETE는 객체를 삭제한 경우 포인터에 0(NULL)값을 명시적으로 넣어주게 되어 있습니다. 저절로 NULL이 되는 것이 아니죠.
그러니 SAFE_DELETE를 사용한 포인터는 객체를 가리키고 있지 않을 시 항상 NULL이 되어야하는 겁니다.
이런 포인터에 대해서 SAFE_NEW를 쓸 수가 있으니까 짝이 맞는다는 말입니다.
그러니 객체 연결을 끊어 버리지 않기 위해서 '포인터'라는 것을 사용한다는건
포인터의 사용례 중에 배치되는 부분이 있지 않느냐는 겁니다.
포인터에 할당되있는 객체를 잃어버리기 싫다면 예로 들어주신 싱글턴 패턴이라던지
다른 표현방식이 있을 꺼라고 생각을 하는대요.
기술적으로는 대칭이지만 실제 사용할 의미가 무엇인지 저는 잘 모르겠네요.
제가 적은 이야기도 기술적인 이야기를 하려고 한게 아니고 포인터라는 말을 쓰면서
약간은 헷갈릴수 있는 개념을 적용 시켜서 무슨 실효성이 있는지에 대한 궁금증에 쓴 이야기였습니다.
지나가다가 첨언을 하자면... SAFE_DELETE는 null 포인터를 delete하지 못하게 하는 역할이 아닙니다.
c++ 스펙 자체에서 delete에 null 포인터가 들어와도 무시하도록 되어있다고 밝히고 있습니다.
단지 게임초보님 말씀대로 한 번 delete한 포인터를 다시 delete하지 못하도록 포인터 변수에 확실히 0을 집어넣어주는 동작이 핵심인 것이지요.
물론 이것도 생각해보면 별로 쓸모가 없다는 게 문제지만요.
이건 제 사견입니다만 SAFE_NEW가 없는 이유는 별 거 아닐 거 같습니다.
사실 SAFE_DELETE라는 것도 표준 매크로도 아니고 그냥 개발자들 사이에서의 일종의 idiom 같은 것일 텐데 그냥 단순히 필요성을 덜 느껴서 그런 게 아닐까 싶습니다.
댕글링 포인터의 참조는 거의 대부분 그 자리에서 바로 런타임 에러가 일어날 테지만, 할당받은 메모리의 주소를 놓쳐버리는 건 메모리 누수를 검사하지 않는 이상은 알아차리기 힘들어서 그렇기도 할 테고,
그리고 또 SAFE_DELETE로 댕글링 포인터의 참조를 어느 정도 막을 수 있겠지만 - 물론 이것도 완벽하지는 않습니다만 - 메모리 주소를 놓치는 것은 단순히 new 뿐만이 아니라 Madkis's님 말씀대로 단순한 대입 연산에 의해서도 일어나기 때문에 별로 효과적이지 않아서 그럴 수도 있겠고요.
마지막으로 이건 주제와는 좀 다른 얘긴데,
사실 c++11에서 shared_ptr와 weak_ptr가 표준화된 이상 웬만하면 이쪽을 사용하는 게 나을 것 같습니다.
위에서 SAFE_DELETE가 완벽하지 않다고 했는데, 왜냐면 SAFE_DELETE가 해당 메모리를 참조하고 있는 모든 포인터를 널 포인터로 만들어주지는 못하기 때문이지요.
가령,
void SomeFunc(Foo* pf) {
// ...
SAFE_DELETE(pf)
// ...
}
...
foo = new Foo(...);
...
SomeFunc(foo);
이런 코드가 있다고 했을 때, SomeFunc 함수를 호출한 이후에도 foo는 여전히 원래 메모리 주소를 가리키고 있을 겁니다.
이래서 완벽하지 않다고 한 것이지요.
뭐 그렇습디다 여하튼.
포인터의 사용을 상당히 제약하는 방법이 되지 않을까요?
새로운 객체를 만들어 기존에 사용하던 쪽에 집어넣어 사용하는 경우가 적지 않은거 같은대 말이죠.
귀찮지만 1줄로 쓸꺼 2줄로 쓰면 우회하는 방법도 있겠고요.
널 포인트가 아닌 것을 A라고 한다면 SAFE_NEW의 매크로를 우회 하는 법으로
pObect temp ;
SAFE_NEW(temp,Object);
a = temp;
식으로 우회도 가능하고요
사용해야 되는 특별한 목적이 있다면 사용하겠지만 일반적으로 쓸 이유는 별로 없어보이네요.