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

C++빌더 팁&트릭
C++Builder Programming Tip&Tricks
[612] TList와 TStringList 클래스의 스택 생성 가능한 버전(수정2) : 치환기능 추가.
김태선 [jsdkts] 8131 읽음    2006-05-22 14:46
VCL 스타일 클래스는 스택에 생성되지 않습니다.
그래서 항상 = new ClassName(); 식으로 new 연산자를 사용해야 합니다.
그런데 프로그램 하다보면 new 로 일일이 할당 받기 귀잖을때가 있습니다.
일반 변수 쓰듯이 그냥 스택이나 알아서 힙에 생성되었으면 하죠.

그래서 꽁수로 TList와 TStringList을 래핑해서 스택에 생성하여 지역변수처럼 사용 가능하게 해 봤습니다.
클래스의 인스턴스가 스택이나 정적메모리 영역에 생성됩니다.

아래는 소스입니다.
//---------------------------------------------------------------------------
// TList와 TStringList 클래스의 스택생성 가능한 버전

class KTList
{
public:
    TList    *p;
public:
    KTList()
    {
        p = new TList;
    }
    ~KTList()
    {
        delete p;
    }
};

class KTStringList
{
public:
    TStringList    *p;
public:
    KTStringList()
    {
        p = new TStringList;
    }
    ~KTStringList()
    {
        delete p;
    }
};
//---------------------------------------------------------------------------
너무 간단하죠.
그야 말로 가볍게 래핑한 것입니다.
앞에 K 접두어는 그냥 붙인 것입니다. 김씨.... ㅡㅡ;

소스를 보면 아시겠지만 변수는 스택에 생성되어도 실제 데이타는 힙에 있습니다.

이제
{
    KTList  FriendList;
    KTStirngList  BookList;
}
식으로 지역변수로 사용 가능합니다.
물론 전역 변수로 사용해도 아무런 문제가 없습니다.

장점은 일단 다루기 간편하다는 것이고, 해당 블럭이나 함수를 벗어나면 자동으로
메모리에서 해제된다는 것입니다.
단점은 해당 아이템을 엑세스할때 포인트 변수  p를 거쳐야 한다는 것입니다.
String  first = BookList.p->Strings[0];

이걸 해결하려면 몇가지 연산자를 오버로딩하면 됩니다.
아래는 몇가지 연산자 오버로딩을 통해서 p를 거치지 않고 사용할수 있게 한 버전입니다.
//---------------------------------------------------------------------------
// TList와 TStringList 클래스의 스택생성 가능한 버전

class KTList
{
private:
    TList    *p;
public:
    KTList()
    {
        p = new TList;
    }
    ~KTList()
    {
        delete p;
    }
    // 연산자 오버로딩.
    TList * operator->() { return p; }
    TList & operator*()    { return *p; }
    operator TList*() { return p; }
};

class KTStringList
{
private:
    TStringList    *p;
public:
    KTStringList()
    {
        p = new TStringList;
    }
    ~KTStringList()
    {
        delete p;
    }
    // 연산자 오버로딩.
    TStringList * operator->() { return p; }
    TStringList & operator*()    { return *p; }
    operator TStringList*() { return p; }
};
//---------------------------------------------------------------------------
p는 더 이상 외부로 노출할 필요가 없으므로 private: 영역으로 이동했습니다.
이제 이렇게 바로 사용할수 있게 됩니다.

String  first = BookList->Strings[0];



스마트포인트를 사용하면 위처럼 굳이 래핑하지 않고도 사용가능합니다.
이곳 팁란에 있는 초간단스마트포인트는 이런 용도로 적합합니다.
{
    smart_ptr<TList>  FriendList(new TList);
    smart_ptr<TStirngList>  BookList(new TStringList);
}
이렇게 스마트포인트를 사용하는 것이나  위 트릭처럼 간단히 래핑하는 것이나 별 차이는 없습니다.
좋아하는 쪽을 사용하면 되겠죠.

하지만 역시 간편성은 KTList 가 낫습니다.
템플릿이 익숙지 않은 분들도 쉽게 쓸수 있죠.

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

여기까지는 꽤 괜잖은 것 같습니다.
KTList, KTStringList는 일반적인 사용에 있어서는 별다른 문제가 없습니다.
하지만 아직 한가지 문제가 있습니다.

KTList, KTStringList 는 모두 생성자에서 객체를 new로 힙에 생성하고
소멸자에서 제거하는데 따른 문제점입니다.
이런 식으로 별도 메모리를 관리하는 클래스의 경우는
어떤 클래스의 멤버변수(클래스)로 들어갈때
그 클래스에서 치환이 일어날때가 문제가 됩니다.
물론 자기 자신만 단독으로 사용하면서 자신을 치환할때도 문제가 되지만 그렇게 이상한 동작을 실무에 하실 분은 별로 없겠죠. 하지만 그럴 경우라도 완벽하게 동작하는게 아무래도 좋습니다.

간단히
class T3
{
public:
    KTList   aa;
    KTStringList  bb;
};

void test()
{
    T3 a, b;
    a = b;
}
의 동작은 아무런 문제가 없어 보입니다만 여기에 심각한 문제가 있습니다.
a = b; 의 클래스간 치환의 경우는
b 메모리의 내용이 a 메모리로 그대로 복사됩니다.
메모리 복사로 memcpy(...); 하는 것과 같습니다.
그러면 a 클래스는 이미 확보하고 있던 메모리를 해제할 기회를 잃게 됩니다.
즉 메모리 누수에 빠지게 되고 b 와 a 는 두번 소멸자를 호출하게 되어 엑세스바이얼레이션
에러를 일으키게 됩니다.
그러므로 해결방법은 = 치환 연산자를 오버로딩 해주야합니다.

여기서 꼭 기억해야 할 중요한 문제는
클래스에서 메모리를 할당받아 관리하는 부분이 있을때, 클래스간  치환을 허용하여 사용할 생각이면,
= 연산자를 반드시 오버로딩하여야 한다는 사실입니다.
위의 예처럼 치환은 = 연산자를 사용할때 뿐만 아니라,
클래스에 바로 값을 대입하여 생성하는 경우도 마찬가지로 필요합니다.


String  s = "aaa"; // 1
s = "test"; // 2
라고 쓴 경우 2번째 라인에서 임시로 String 객체(인스턴스)가 생성되고 String 클래스의 = 연산자를
통해 임시객체에서 원래객체로의 값의 이동이 일어나게 됩니다.
= 연산자를 오버로딩하지 않으면 단순 메모리 복사가 일어나므로
"aaa"; 를 가르키던 포인트 값이 변해 "aaa"; 저장되어 있던 공간은 해제될 기회가 없어지므로
메모리 누수를 만나게 됩니다.


아래는 이 문제를 개선한 버전입니다.

//---------------------------------------------------------------------------
// TList와 TStringList 클래스의 스택생성 가능한 버전

class KTList
{
public:
    TList    *p;
public:
    KTList()
    {
        p = new TList;
    }
    ~KTList()
    {
        delete p;
    }
    // 연산자 오버로딩.
    KTList & operator=(const KTList& src) { p->Assign(src.p); return *this;    }
    TList * operator->() { return p; }
    TList & operator*()    { return *p; }
    operator TList*() { return p; }
};

class KTStringList
{
public:
    TStringList    *p;
public:
    KTStringList()
    {
        p = new TStringList;
    }
    ~KTStringList()
    {
        delete p;
    }
    // 연산자 오버로딩.
    KTStringList & operator=(const KTStringList& src) {    p->Assign(src.p); return *this; }
    TStringList * operator->() { return p; }
    TStringList & operator*()    { return *p; }
    operator TStringList*() { return p; }
};
//---------------------------------------------------------------------------


간단히 팁으로 올리려고 했는데, 강좌가 되어 버렸군요.
이전에 치환할 경우를 생각지 않고 올려서 보신분들께 죄송하다는 말씀을 전합니다.


첨부 파일은 Template TList 에 KTList 와 KTStringList를 추가한 것입니다.
연관성이 높아 그냥 한데 묶었습니다.
서비 [suby]   2006-05-27 16:45 X
오 앞으로 편리해질거 같습니다 ^^
좋은 팁 감사합니다.

+ -

관련 글 리스트
612 TList와 TStringList 클래스의 스택 생성 가능한 버전(수정2) : 치환기능 추가. 김태선 8131 2006/05/22
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.