안전한 메모리 Release 템플릿

프로그래밍 일반에 관한 포럼입니다.

Moderator: 류광

Locked
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

안전한 메모리 Release 템플릿

Post by 쌀밥 »

별건 아니지만... 올려봅니다.

저는 이런걸 만들어서 delete 할일이 있을때 이걸 사용하는데
혹시 더 멋진 방법이 있지 않을까 싶어서...

Code: Select all

//-----------------
// 안전한 메모리 Release 템플릿

template < typename _Ty >
inline void _Delete_( _Ty & var )
{
    if( var )
        delete var;
    var = NULL;
}
template < typename _Ty >
inline void _DeleteArray_( _Ty & var )
{
    if( var )
        delete [] var;
    var = NULL;
}
I want to live in korea, making programs, but...
http://wrice.egloos.com
비회원

Post by 비회원 »

NULL 에 대고 delete 해도 문제 없는걸로 알고 있습니다...
류광
Posts: 3803
Joined: 2001-07-25 09:00
Location: GPGstudy
Contact:

Post by 류광 »

윗분 지적대로 NULL에 대해 delete를 해도 문제가 없습니다.

var가 NULL이면 예외를 던지는 게 어떨까요? "당신이 유효한 포인터라고 생각하고 있던 것이 사실은 NULL이었어요"라는 의미로요...

그리고 사소한 스타일 문제 몇 가지...

우선 NULL은 표준이 아닙니다. Windows.h 같은 것을 포함시켜야 한다는 전제가 없는 한, 0을 사용하는 게 좋겠네요.

두 번째로 식별자 이름을 _로 시작하는 것은 바람직하지 않습니다. _로 시작하는 식별자는 컴파일러나 표준 라이브러리 구현에 예약된 것으로 간주하는 게 안전합니다.

세 번째로 두 함수 모두 매개변수가 참조인데 delete의 특성 상 *로 하는 게 좋을 것 같습니다.

뭐 기능상으로는 차이가 없지만, 예를 들어 int a; _Delete_(a); 같은 잘못된 사용에 대한 컴파일러의 오류 메시지가 좀 차이가 납니다... &의 경우에는 _Delete_()의 delete에서 오류가 나고, *의 경우에는 _Delete_(a); 자체에서 오류가 납니다. 사소한 차이겠지만, _Delete_(a);에서 직접 오류가 나는 게 더 직관적인 것 같습니다.
자갈공명
Posts: 60
Joined: 2003-06-25 19:31
Location: ...
Contact:

Post by 자갈공명 »

같은건데..전 매크로로 씁니다..( 아마 dx예제 어디쯤에선가도 같은게 있었던거 같습니다. )

Code: Select all

#define SAFEDELETE(x)		if(x != NULL) { delete x; x = NULL; }
#define SAFEDELETEARRAY(x)	if(x != NULL) { delete [] x; x = NULL; }
#define SAFERELEASE(x)		if(x != NULL) { x->Release(); x = NULL; }
궁금이
Posts: 237
Joined: 2005-01-19 11:06
Location: ProjectS

Post by 궁금이 »

저두 자갈공명님처럼.. 매크로 쓰는뎅..

dxutil.h 요 파일에서 본거 같은데.. -0-
seeper
Posts: 1483
Joined: 2003-06-06 23:19
Contact:

9.0c 이전 프레임워크에 dxutil.h 에 보면

Post by seeper »

Code: Select all

#define SAFE_DELETE(p)       { if(p) { delete (p);     (p)=NULL; } }
#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p);   (p)=NULL; } }
#define SAFE_RELEASE(p)      { if(p) { (p)->Release(); (p)=NULL; } }
라고 있죠. 저도 자주 쓰는 매크로...
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

Post by 쌀밥 »

오.... 좋은 조언 감사드립니다..
조금 개선해 보았는데 이거면 어떨까요?

Code: Select all

template < typename _Ty >
inline void Delete( _Ty * & var )
{
    assert( "Invalid release!!" && var );
    delete var;
    var = 0;
}
template < typename _Ty >
inline void DeleteArray( _Ty * & var )
{
    assert( "Invalid release!!" && var );
    delete [] var;
    var = 0;
}
이렇게 하면 if 문도 한번 줄고, 류광님이 말씀 하신데로 컴파일시 에러도 뱉어 낼것 같습니다..
*PS: 버그가 있었네요; 테스트 안해보고 올려버려서;;; 수정했습니다
Last edited by 쌀밥 on 2005-02-25 01:28, edited 4 times in total.
I want to live in korea, making programs, but...
http://wrice.egloos.com
비회원

Post by 비회원 »

함수자로 만들어 주시는게 generic알고리즘에 적용하기 더 좋을거 같아요.
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

Functor 를 사용한 구현..

Post by 쌀밥 »

감사합니다. 점점 재미있어지는 군요.. ㅎㅎ
별거 아니라고 생각했지만 글로 쓰길 잘했다는 생각이...

아래는 functor (함수자) 를 사용한 코드 입니다.

Code: Select all

struct tagDelete
{
    template < typename _Ty >
    inline void operator()( _Ty * & var )
    {
        assert( "Invalid release!!" && var );
        delete var;
        var = 0;
    }
} Delete;

struct tagDeleteArray
{
    template < typename _Ty >
    inline void operator()( _Ty * & var )
    {
        assert( "Invalid release!!" && var );
        delete [] var;
        var = 0;
    }
} DeleteArray;
I want to live in korea, making programs, but...
http://wrice.egloos.com
비회원

내용을 읽는중에 궁금한 사항이...?

Post by 비회원 »

template, macro...를 작성하실때, delete와 delete []의 두가지 경우에 대한
방법을 구현하셨는데요...

개인적으로는 습관적으로 int* a = new int; 보다는 int* a = new int[1];를
사용하게 되구요(오랜전에 책에서 읽은 내용입니다.. ^^;) 제거시에는 한가지
방법으로 delete [] a;를 사용합니다.

여기서 궁금한점이(^^;)... '...= new int' 와 '...= new int[1]'와의 차이점이
어떤것이 있을까요?... 장단점이라고 해야하나...

짧은 지식으로는 별반 차이가 없어 보입니다만...(ㅡ.ㅡ;)
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

Re: 내용을 읽는중에 궁금한 사항이...?

Post by 쌀밥 »

음... new int[1] 이라... 재미있네요..
저는 그런 방법을 처음 들었습니다...
괜찮으시면 어떤 책에서 그 내용을 보셨는지 참고 할 수 있도록 알려주시면 감사하겠습니다..

그리고 제가 지금 책이 없어서 (PC방이라) 확인해보지 못하고 적는거지만,
new int; 형태 (그러니까 array 형태가 아닌것) 를 완전히 쓰지 않을 수 있을까요?

예를 들면, 생성자 아규먼트 값을 필요로 하는 경우...

Code: Select all

class cMyClass;
cMyClass * kTmp = new cMyClass(30)[1];
이렇게 되나요?? 문법이 정확히 모르겠는데...;
제 어렴풋한 기억으로는 effective c++ 에서 이런 종류의 난해함에 대해 이야기가 나왔던것 같은데요...
I want to live in korea, making programs, but...
http://wrice.egloos.com
conaman
Posts: 313
Joined: 2002-07-27 12:01
Location: (주)게임빌
Contact:

..

Post by conaman »

여기서 궁금한점이(^^... '...= new int' 와 '...= new int[1]'와의 차이점이
어떤것이 있을까요?... 장단점이라고 해야하나...
제가 Visual C++에서만 확인한 것이라 모든 컴파일러에서 같은지는 모르겠지만

CFoo* pfoo1 = new CFoo;

CFoo* pfoo2 = new CFoo[1];

는 겉으로 볼 때 아무 문제 없으나 메모리를 살펴보면 다르게 잡히는 것을 알 수 있습니다.

pfoo1은 특별한 설명이 필요없을 거구요...

pfoo2가 만일 100이라는 값을 가지게 되었다면 96번지엔 int로 1이라는 값이 들어가 있을 겁니다.

int, float같은 기본 변수들엔 이런 정보들이 들어가지 않지만 class 형태로 되어 있는 것엔 다 이 값이 들어가게 됩니다. 물론 C++ 표준대로 한다면 int도 class여야 하겠지만 속도 때문에 다들 편법을 쓰는 것 같네요...

만일 CFoo* pfoo3 = new CFoo[5]; 라면 static_cast<char *>(pfoo3)-4 의 위치에 int값으로 5가 들어가 있을 겁니다.

이 값은 나중에 delete[]로 지울 때 소멸자를 불러주는데 사용이 됩니다.

delete는 소멸자를 한번만 호출하게 되죠...
그렇기 때문에 delete[] pfoo3를 하지 않고 delete pfoo3을 해 주면 메모리 누수가 생기게 됩니다.

5년 전에 테스트 해 본 건데 도움이 되면 좋겠습니다.
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

함수자 (Functor) 에 대해...

Post by 쌀밥 »

제가 함수자를 처음 써봐서 틀린점이 있었네요...
codeproject 를 뒤져 보니 함수자에 대해 여러가지 이야기가 있고, STL 레퍼런스 책에 보니까 function object (함수 객체) 라는 말로 되어있네요..;

아무튼 함수자 형태로 다시 작성해보았습니다.
이번에 올리는게 아마 확실히 맞을듯...

Code: Select all

template < typename _Ty >
struct DeleteFunctor : std::unary_function < _Ty, void >
{
    void operator() ( _Ty * & var )
    {
        assert( "Invalid release!!" && var );
        delete var;
        var = 0;
    }
};

template < typename _Ty >
struct DeleteArrayFunctor : std::unary_function < _Ty, void >
{
    void operator() ( _Ty * & var )
    {
        assert( "Invalid release!!" && var );
        delete [] var;
        var = 0;
    }
};
근데 이렇게 functor 로 사용하게 되면 그냥 일반적으로 사용하는 거랑은 문법이 많이 달라지네요..
우선 객체를 생성시킨뒤에 아규먼트로 넘겨주는 형태가 되어야 하는데..

Code: Select all

DeleteFunctor<int*> DeleteFunctorIntPtr;
이런식으로요;;
이건 너무 불편한듯...;

제가 functor 를 잘 못 이해한게 있거나 코드에 틀린점이 있으면 지적해주세요...
I want to live in korea, making programs, but...
http://wrice.egloos.com
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

Re: 내용을 읽는중에 궁금한 사항이...?

Post by 쌀밥 »

쌀밥 wrote:예를 들면, 생성자 아규먼트 값을 필요로 하는 경우...

Code: Select all

class cMyClass;
cMyClass * kTmp = new cMyClass(30)[1];
이렇게 되나요?? 문법이 정확히 모르겠는데...;
제 어렴풋한 기억으로는 effective c++ 에서 이런 종류의 난해함에 대해 이야기가 나왔던것 같은데요...
visual c++ 7.1 에서 두가지로 해봤는데요..

Code: Select all

class cMyClass
{
public:
    cMyClass( int a ) { };
};
cMyClass * c = new cMyClass(3)[1];
cMyClass * c = new cMyClass[1](3);
둘다 안되는군요..
그리고 effective c++ 에서 봤다고 생각했는데, 책을 뒤져 봐도 그 내용이 없군요; 어디 다른 책이었던듯;;
Last edited by 쌀밥 on 2005-02-26 12:23, edited 1 time in total.
I want to live in korea, making programs, but...
http://wrice.egloos.com
seeper
Posts: 1483
Joined: 2003-06-06 23:19
Contact:

Post by seeper »

gcc 테스트

Code: Select all

class cMyClass
{
public:
    cMyClass( int a ) { };
};

main()
{
    cMyClass * c = new cMyClass[1](3);
}
이렇게 하니까 되는군요.. ( new cMyClass(3)[1] 은 오류입니다.)

VC7에 오류를 살펴보니까... (VC6도...)
컴파일러가 지원하지 않으니 사용자가 직접하라고 하내요.. --;
제가 생각할 수 있는 수준은 결국 하나씩 new 하는수 밖에 없는것 같군요.

결국 new할때 모두 배열로 일괄적으로 사용하는건 일반적인 VC환경에서는 힘든것 같습니다.
아님 생성자에 인자를 모두 포기해야하는데... 그다지 좋은 생각 같지는 않네요...
비회원

제생각엔,,,

Post by 비회원 »

제생각엔 그냥 assert없이 쓰는것이 가장 좋을듯 합니다.

그리고 if ( p ) delete p;

이건 아무 의미 없다고 봅니다.

어짜피 NULL을 delete 해봤자 아무문제 없고,,

또 p에 쓰레기 값이 들어 있었다면,, 거기다가 delete 할경우.. 에러나는것은 뻔하지 않습니까..

결국 예외로 잡아내지 않는이상.. 아무 쓸모도 없단 얘기죠..

또, assert 도 필요 없다고 생각됩니다.

만약 어떤 객체가 많은 맴버로 이루어져 있고, 반쯤은 초기화에 성공하고 나머진 초기화에 실패했다 했을때,

이미 초기화에 성공한 맴버들만을 골라서 제거하기는 아마 불가능 할꺼 입니다.

그냥 쓰는게 가장 무난할꺼 같다는 생각이 듭니다..
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

Re: 제생각엔,,,

Post by 쌀밥 »

비회원 wrote:또 p에 쓰레기 값이 들어 있었다면,, 거기다가 delete 할경우.. 에러나는것은 뻔하지 않습니까..
에러가 난다고 생각할 수 없을 것 같습니다.
'어떻게 동작할 지 알 수 없다' 가 맞겠죠.

에러가 안나고 그냥 지나가 버릴 수도 있습니다.
그러다가 나중에 완전 쌩뚱 맞은 곳에서 에러가 발생해 버리면 디버깅 하기 어려워질 겁니다..;
비회원 wrote:만약 어떤 객체가 많은 맴버로 이루어져 있고, 반쯤은 초기화에 성공하고 나머진 초기화에 실패했다 했을때,
이미 초기화에 성공한 맴버들만을 골라서 제거하기는 아마 불가능 할꺼 입니다.
assert 를 넣은것은 생성과정에서 메모리 할당에 실패 한 경우를 잡아 내기 위해서 넣은 것이 아니라
이미 delete 가 된 메모리를 중복해서 delete 하는 경우를 찾아내기 위한 것입니다.
생성 과정에서 발생하는 문제는 new 의 return 값이 null 인지 확인하거나 예외를 받아주는 방식으로 해결해야합니다.
I want to live in korea, making programs, but...
http://wrice.egloos.com
류광
Posts: 3803
Joined: 2001-07-25 09:00
Location: GPGstudy
Contact:

Post by 류광 »

첫 번째 버전의 의도는 널 포인터를 잘못 삭제하는 일을 방지하는 것이었는데, 다른 분이 지적하셨듯이 널 포인터의 삭제는 널 연산이므로 애초 의도가 무의미해졌습니다.

따라서 이후 버전들의 의도는:
1. 포인터가 널이 아니었다고 생각했던 오해를 찾아내는 것
2. 삭제한 포인터에 명시적으로 널을 배정하는 것

assert는 1번을 보장하는 것이므로 뺄 수가 없겠습니다..

그런데 이런 템플릿이나 SAFE_DELETE 류의 매크로들은 똑똑한 포인터로 대체하는 게 정답(또는 추세?^^)이 아닐까 합니다. 그리고 메모리 할당 실패는 애초에 예외로 잡는 게 좋겠구요.
Locked