C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 강좌/문서
C++Builder Programming Tutorial&Docments
[103] [KTS의 C++빌더] Template new - 자동으로 객체가 소멸하는 스마트 new 클래스
김태선 [cppbuilder] 20487 읽음    2006-06-08 01:36
//---------------------------------------------------------------------------
// 김태성의 C++빌더 : Template new - 자동으로 객체가 소멸하는 스마트 new 클래스
//---------------------------------------------------------------------------

C++에서는  메모리를 할당받을 때는 new 연산자를 사용합니다.
가령 정수형 배열로 크기 100을 가지는 메모리는
int   *pInt = new int[100];
와 같이 할당 받습니다.
그러면 메모리는 힙에 확보되고  pInt는 그 포인트를 가지게 됩니다.
사용한 메모리는
delete[] pInt;
와 같은 방법으로 제거해야 합니다.

new 로 메모리를 할당 받았으면 반드시 delete 소멸시켜야 하는 것이 C++의 법입니다.
delete 로 적정하게 메모리를 소멸시키지 않고 다시 메모리를 할당 받을때, 보통 이를  메모리 누수라고 합니다.
메모리 누수는 미션크리티컬한 프로그래밍에 있어서는 반드시 없애야 합니다.
서버용 프로그래밍에서는 약간의 누수가 수백만번의 접속이 이루어지는 사이 엄청나게 누적이 될 수 있기 때문에
반드시 메모리 누수를 제거해야만 합니다.

보통 프로그래밍할 때 new 와 delete 는 조금만 신경써서 짝을 맞추면 보통 문제를 일으키지 않았습니다.
하지만 예외 상황에서 delete 이전에 루틴을 빠져나가거나, 루틴이 복잡해서 delete 짝을 못 맞추는 경우도 있습니다.
아무리 고수 플머라도 프로그래밍은 간편하고 퍼포먼스가 떨어지지 않는 한도 내에서 자동화 된 것이 좋습니다.

왜 꼭 new 로 할당받고 delete 로 짝 맞춰서 없애야 할까? 좀더 간단히 할수 없을까?
이러한 요구에 응해 나온 것이 바로 스마트포인트입니다. 유명한 것으로는 boost 라이브러리의
scoped_ptr 과 윈도 라이브러리의 auto_ptr, 그리고 필자가 만들어 소개한 초간단 스마트포인트인 smart_ptr 등이
있습니다.

그런데 여기서 필자는 new 로 할당받을때 자동으로 delete 되는 new 를 대신할 수 있는 클래스를
제작하면 어떨까 생각하게 되었습니다.
new  와 같으나 지역변수처럼 자동으로 소멸하게 하고, 그 요소를 엑세스할때 편리하게 하면
프로그램이 좀더 간편해지리라 생각되더군요.
물론 스마트포인트를 이용해서도 부분적으로 가능하지만 이왕이면 배열로 다룰 수 있게,
new 를 대신하는 방법을 사용하고 싶더군요.
(smart_ptr은 단일 객체에 대한 할당이며, Tnew는 배열로 할당하는 경우이며 Tnew 에 사용되는
클래스는 초기 생성인자를 생략할 수 있는 형이어야 합니다)

그래서 Template new 즉 Tnew 라는 new 기능을 대신하는 클래스를 간단히 제작하게 되었습니다.
자동으로 객체가 소멸해야 하므로 new 연산자를 오버라이드해서는 구현할 수가 없어, 클래스로 제작한 것입니다.


문법은
Tnew<변수/구조체/클래스형>  변수명(크기,  메모리초기화여부);
이렇게 구성됩니다.  메모리 초기화 여부는 생략가능하며 생략시는 자동 0으로 채우게 됩니다.
지역변수나 멤버변수, 전역변수 어디에 놓아도 동작합니다.

Tnew<int, 500>  array; 와 같은 식으로 하지 않고
Tnew<int>  array(500); 식으로 한 이유는
Tnew<int, 500>  array; 의 경우는 배열의 크기를 동적으로 바꿀수가 없기 때문입니다.
이것은 컴파일하는 순간에 형과 상수값이 결정되어야 하는 템플릿의 특성 때문에,
실시간으로 동적 배열을 구성할 수 있도록 하기 위함입니다.


아래는 템플릿 Tnew 클래스 소스입니다.
첨부한 Tnew.h 파일과 내용이 같습니다.

#ifndef TnewH
#define TnewH
//---------------------------------------------------------------------------
// Template new
//        delete 가 필요없는 스마트 new.
//      메모리를 힙에 할당받고 자동으로 속은 0로 초기화 한다.
//        그러므로, 기본형과 String 외 typename T가 생성자를 거친후 0으로 초기화 되면 안되는 클래스를 사용하면 안되나,
//        사용해야 할때는 생성자에서 ZeroMemory가 동작하지 않아야 하므로 생성자(크기,false) 를 인자로 준다.
//        참고:String, TStringList, TList 는 생성된 직후 메모리가 모두 0이므로, 0으로 재초기화 가능하지만 일부러 그럴 필요없다.
//
//    Written by 김태성 cppbuilder@naver.com

template<typename T>
class Tnew
{
private:
    T        *p;
    int        m_Size;
public:
    __property int Count = { read = m_Size };
public:
    Tnew(int SIZE, bool bClear = true)
    {
        p = new T[SIZE];
        if (bClear)
            ZeroMemory(p, sizeof(T) * SIZE);
        m_Size = SIZE;
    }
    ~Tnew()
    {
        delete[] p;
    }
    // 연산자 오버로딩.
    Tnew& operator=(Tnew& src)
    {
        delete[] p;
        p = new T[src.Count];
        memcpy(p, (T *)src, src.Count * sizeof(T));
        m_Size = src.Count;
        return *this;
    }
    T* operator->() { return p; }
    T& operator*()  { return *p; }
    operator T*()     { return p; }
};
//---------------------------------------------------------------------------
#endif


다음은 테스트 코드입니다.
//---------------------------------------------------------------------------
void    add(String msg){    Form1->Memo1->Lines->Add(msg); }

void __fastcall TForm1::BTnewClick(TObject *Sender)
{
    // 메모리 누수 체크.
    int  *p = new int;
    add(String().sprintf("heap before: %08X", p));
    delete p;
    {
        Tnew<char>  buf(10000), buf2(10000);    // char *buf = new char[10000]; char *buf2 = new char[10000];
        wsprintf(buf, "오 필승 코리아 %d", buf.Count);
        add("\r\nchar 배열이라우");
        add((char *)buf);
        buf2 = buf;                    // 직접 대입도 가능. 메모리 내용이 안전하게 복사된다. 두 객체의 크기가 달라도 상관없다.
        add((char *)buf2);

        Tnew<String> slist(5);        // String *slist = new String[5];
        slist[0] = "하하하하";
        slist[1] = "호호호";
        slist[2] = "히히";
        slist[4] = "마지막";
        add("\r\n문자열 String 배열이라우");
        for(int c = 0; c < slist.Count; c++)
        {
            add(String(c) + "번째 : " + slist[c] + "   Length: " + slist[c].Length());
        }
        add("");
        Tnew<String> slist2(5);        // String *slist2 = new String[5];
        slist2 = slist;             // 대입 복사
        for(int c = 0; c < slist2.Count; c++)
        {
            add(String(c) + "번째 : " + slist2[c] + "   Length: " + slist2[c].Length());
        }

        add("\r\n정수형 배열이라우");
        Tnew<int>  ar(5, false);    // int *ar = new int[10]; 선언후 0으로 초기화를 하지 않았다.
        for(int c = 0; c < ar.Count; c++)
        {
            add(ar[c]);
        }
        for(int c = 0; c < ar.Count; c++)
        {
            ar[c] = c;    // 값대입
        }
        for(int c = 0; c < ar.Count; c++)
        {
            add(ar[c]);
        }
    }
    p = new int;
    add(String().sprintf("heap after: %08X", p)); // 메모리 누수 없음을 확인함.
    delete p;
}
//---------------------------------------------------------------------------

화면에 찍히는 결과입니다.
Memo1
heap before: 00D28A34

char 배열이라우
오 필승 코리아 10000
오 필승 코리아 10000

문자열 String 배열이라우
0번째 : 하하하하   Length: 8
1번째 : 호호호   Length: 6
2번째 : 히히   Length: 4
3번째 :    Length: 0
4번째 : 마지막   Length: 6

0번째 : 하하하하   Length: 8
1번째 : 호호호   Length: 6
2번째 : 히히   Length: 4
3번째 :    Length: 0
4번째 : 마지막   Length: 6

정수형 배열이라우
0
19
-1530852851
-54004036
-1075267296
0
1
2
3
4
heap after: 00D28A34

//---------------------------------------------------------------------------

heap의 before, after 번지가 동일하죠.
메모리는 누수없이 잘 동작함을 알수 있습니다.
정수 배열을 잡을때 메모리 초기화하지 않은 경우,
임의의 값이 들어가 있음도 확인해 볼 수 있습니다.


위의 예에서 볼수 있듯이 간단히 String 배열로 다룰 수도 있고
단순히 메모리 공간 확보 용도로 사용할 수 도 있습니다.
이제
char *p = new char[1000];
;
delete[] p;
와 같은 코드는

Tnew<char> p(1000);
으로 간편하게 대치 가능하며,
함수의 리턴위치 신경써가며 delete 문장을 맞춰 넣을 필요가 없습니다.
그기에 메모리 초기화도 자동으로 이루어 집니다.

물론
char *p = new char[1000];
로 메모리를 할당 받았을 경우와 마찬가지로,
p[0] = 1, p[1] = 'C';
식으로 값을 할당하거나 읽는 것과 동일하게 처리할 수 있습니다.
여기서 하나 의문이 들죠.
Tnew<char> p(1000);
을 했는데 어째서
p[0] = 1; p[2] = 'B';
식으로 처리가 가능한 것일까? 배열연산자 [] 에 대한 처리를 하지 않았는데 사용에 지장이 없죠.

비밀은 클래스내에 T*() 형의 연산자 오버로딩에 있습니다.
    operator T*()     { return p; }
p[1] = 'C';
와 같은 식은 char 형이 대입되므로, p 는 char * 형을 참조하여 배열의 위치를 결정해야 하는데...
p[1] 은 *(p + 1) 과 같은 표현입니다. 즉 하나는 배열이고 하나는 포인트인데 둘다 같의 의미입니다.
하지만 컴파일러 입장에서는 *(p + 1) 이 보다 컴파일한 실제 결과에 가깝습니다.
즉 *(p + 1) = 'C'; 와 같은 표현이므로 컴파일러는 char * 에 대한 연산자가 오버로딩되어 있는지 보는 것입니다.
그런데 위에
    operator T*()     { return p; }
로 배열의 선두 번지를 리턴하게 연산자가 오버로딩되어 있죠.
그래서 *(p + 1) = 'C'; 식은 성립하고 p[1] = 'C'; 와 동일한 문장이 되는 것입니다.
그러므로 배열연산자인 [] 에 대한  연산자 오버로딩이 되어 있지 않음에도 불구하고,
p[첨자] 를 쓰는 표현식을 쓸수 있는 것입니다.

Tnew<char> p(1000), p2(200);
p = p2;  식으로 간편하게 객체를 복사하는 것도 쉽게 이루어집니다.
int  len = strlen(p); 식으로 char *  가 필요한 곳은 형변환없이 바로 대입하여 처리할 수 있습니다.
물론 strcpy(p, p2); 식으로해도 아무런 문제가 없습니다.
이러한 사항은 Tnew<char> 형 뿐만 아니라 다른 형도 동일하게 적용됩니다.
다만, String 형을 인자로 받는 곳은 char * 형을 바로 대입할 수 있으나
Tnew<char>형은 바로 대입할 수 없으므로, add((char *)p); 식으로 한번 명시적으로 형의 캐스팅을
지정해 주면 String 형으로 자동 변환됩니다.
void add(String msg); 함수이므로 이렇게 동작하는 것이죠.

예제에서 보시면 아시겠지만
다루는 것도 매우 간편하고 분명합니다.

프로그래밍에 좀더 편리하리라 생각되는데,
사용할때는 분명히 원리를 이해를 하고 사용해야 한다는 점을 기억하시기 바랍니다.

-> 연산자를 오버로딩 했으므로 -> 연산자를 사용하게 되면 배열의  첫번째 요소를 불러 오게 됩니다.
위의 예제에서
slist->Length(); 와 같은 것이 가능한데, 곧 slist[0].Length(); 와 같습니다.
그러므로 -> 연산자는 사용할 필요가 없으나, 있다고 반드시 나쁠만한 이유도 없어 그냥 두었습니다.
참고로, 배열 크기를 1로 잡으면 당연히 단일 객체에 대한 할당이 이루어집니다.
이 경우는 배열첨자 없이 -> 연산자로 바로 엑세스하는 것이 편리 할 것입니다.

실무에 적용해서 많이 써보면서 약간의 조정은 차후에 할 것입니다.

그럼.. 보다 편리하고 자동화된 프로그래밍을 즐기시기를...


//---------------------------------------------------------------------------
// 김태성 cppbuilder@naver.com
// 이 강좌는 아무 곳에나 비상업적인 용도에 한해 내용 변경 없이 원래의 출처를 명시하여
// 배포할 수 있습니다. 단, 필자의 이메일로 게제 사실을 통보해야 합니다.
//---------------------------------------------------------------------------

+ -

관련 글 리스트
103 [KTS의 C++빌더] Template new - 자동으로 객체가 소멸하는 스마트 new 클래스 김태선 20487 2006/06/08
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.