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
[206] boost의 shared_ptr에서 enable_shared_from_this를 못쓰는 경우
helloman [helloman] 25600 읽음    2010-03-22 00:42
boost의 shared_ptr을 써봤는데 다 좋은데 치명적인 문제점이 있더군요.
밑에 Lyn님께서 설명해주신 상황이죠.
근본적으로 아래와 같은 문제입니다.

TYPE* p = new TYPE;
shared_ptr a(p);
shared_ptr b(p);


이렇게 하면 a와 b가 별개로 존재하게 되죠.
a의 참조 카운트가 0이 되서 객체가 사라져도 b는 그것을 모르고
여전히 유효하다고 생각해서 객체에 접근하려고 하거나 이미 지워진
객체를 또다시 지우는 댕글링 포인터 문제가 발생합니다.
이 문제를 해결하는 것이 enable_shared_from_this를 상속받아서
사용하는 것입니다.
그러나 문제가 있습니다. c++ 빌더는 VCL에서 상속받은 클래스들은
다중상속을 허용하질 않습니다. 즉 enable_shared_from_this를 상속받는 것
그 자체가 불가능하게 됩니다.
저는 제가 만든 새로운 프로그램 패러다임인 관계지향 프로그래밍을 위해서
모든 클래스에 베이스로 TSocialObject를 깔고 있는데 이것이 TObject에서
온 것이기 때문에 모든 클래스가 다중상속이 불가능한 상태입니다.
그리고 제가 열심히 테스트해본 결과 enable_shared_from_this 자체에도
문제가 있습니다.

class TYPE: public enable_shared_from_this
{
};

class TYPE2: public TYPE
{
    TYPE2()
    {
        shared_ptr p = shared_from_this();
    }
};


위의 코드는 에러를 발생시킵니다.
enable_shared_from_this는 안타깝게도 그 능력을 상속시킬 수가 없습니다.
(템플릿에 의존하기 때문입니다.)
즉 모든 클래스가 enable_shared_from_this를 직접 상속받아야 합니다.

class TYPE: public enable_shared_from_this
{
};

class TYPE2: public TYPE, public enable_shared_from_this
{
};


이런 식으로 말입니다.
그런데 enable_shared_from_this는 가상 클래스가 아닙니다.
내부에 멤버변수 weak_this_가 있어서 8바이트를 잡아먹습니다.
즉 모든 클래스에 enable_shared_from_this를 상속시키면 상속이 누적될수록
인스턴스에 쓸데없는 쓰레기 메모리 8바이트가 계속 추가로 생긴다는 문제입니다.
이걸 피하기 위해서 베이스 클래스에서 한번만 멤버변수를 정의하면
상속받은 클래스에서도 계속 사용할 수 있어야만 한다고 생각했습니다.

즉 다중상속이 되지 않아도 쓸 수 있어야 하고
베이스 클래스에서 한번만 정의하면 쓸 수 있도록 한다..
정말 어려운 조건이었습니다만 해냈습니다. ㅎㅎ...
물론 타입을 무시하고 억지로 스태틱 캐스팅을 하기 때문에 감히 권할만한 코드는 아닙니다.
그리고 enable_shared_from_this 처럼 자동으로 백그라운드에서 동작이 이뤄지질 않습니다.
제가 boost 코드를 함부로 고칠 수가 없기 때문입니다.
그러나 동작에는 아무런 문제가 없습니다. 버그도 없습니다.

구현방법은 2가지가 있습니다.
첫번째 방법은 자동으로 초기화가 이뤄집니다.
클래스 선언에다가 매크로만 추가해주면 그걸로 작업은 끝이고
개발자가 수동으로 초기화 함수같은걸 호출할 필요가 없습니다.
그러나 이 방법은 오직 shared_from_this() 함수를 사용할 수 있게만 합니다.
shared_from_this()로 생성된 shared_ptr은 별개의 트리로 존재하기 때문에
제가 맨위에 설명했던 문제점은 그대로 존재하게 됩니다.

TYPE* p = new TYPE;
shared_ptr a(p);
shared_ptr b(p);


바로 이 문제점 말입니다.
즉 보다 편리하고 간단하게 사용할 수 있지만 근본적인 문제는 남아있다는 것입니다.
아래는 그 구현입니다.

#define ENABLE_SHARED_FROM_THIS(TYPE)
private:
    boost::shared_ptr shared_from_this()
    {
        return *((boost::shared_ptr*)fake_this.dummy);
    }
    boost::shared_ptr shared_from_this() const
    {
        return *((boost::shared_ptr*)fake_this.dummy);
    }

#define BASE_ENABLE_SHARED_FROM_THIS(TYPE)
private:
    struct fake_shared_from_this
    {
        char dummy[sizeof(boost::shared_ptr)];
        fake_shared_from_this()
        {
            static int diff_between_this = -2;
            if ( diff_between_this < 0 )
            {
                if ( diff_between_this == -2 )
                {
                    diff_between_this = -1;
                    TYPE* test_obj = new TYPE;
                    diff_between_this = (int)(&test_obj->fake_this) - (int)test_obj;
                    delete test_obj;
                }
                else return;
            }

            boost::shared_ptr temp((TYPE*)((int)this - diff_between_this));
            memset(dummy, 0, sizeof(dummy));
            *((boost::shared_ptr*)dummy) = temp;
        }
    };
    friend fake_shared_from_this;
private:
    boost::shared_ptr shared_from_this()
    {
        return *((boost::shared_ptr*)fake_this.dummy);
    }
    boost::shared_ptr shared_from_this() const
    {
        return *((boost::shared_ptr*)fake_this.dummy);
    }
protected:
    fake_shared_from_this fake_this;
    template 
    boost::shared_ptr shared_from_this(SUBTYPE* p = NULL)
    {
        return *((boost::shared_ptr*)fake_this.dummy);
    }
    template 
    boost::shared_ptr shared_from_this(SUBTYPE* p = NULL) const
    {
        return *((boost::shared_ptr*)fake_this.dummy);
    }


두개의 매크로가 있습니다.

ENABLE_SHARED_FROM_THIS(TYPE)
BASE_ENABLE_SHARED_FROM_THIS(TYPE)

베이스 클래스에서 BASE_ENABLE_SHARED_FROM_THIS를 한번만 사용하면 됩니다.
그이후 파생 클래스들에서는 ENABLE_SHARED_FROM_THIS 만 사용하면 됩니다.
아래는 그 예입니다.

class test
{
    BASE_ENABLE_SHARED_FROM_THIS(test)
};

class test2 : public test
{
    ENABLE_SHARED_FROM_THIS(test2)
};


이렇게 하면 끝입니다. shared_from_this()를 자유롭게 사용할 수 있습니다.
그러나 이 방법보다 더 개선된 두번째 방법이 있습니다.
두번째 방법은 근본적으로 boost 라이브러리 안의 enable_shared_from_this가 사용하는
방법을 흉내낸 것입니다.
다만 shared_ptr 자체의 소스를 건드릴 수 없기 때문에 사용하는 방법에 제약이 가해집니다.
특정한 방법으로 사용해야만 됩니다.

일단 구현 소스부터 설명합니다.

#define NEW(TYPE)                    TEnableSharedFromThis::ChangeToSharedPointer(new TYPE)
#define NEW_PARAM(TYPE, constructor) TEnableSharedFromThis::ChangeToSharedPointer(new TYPE constructor)
#define NEW_ARRAY(TYPE, size)        TEnableSharedFromThis::ChangeToSharedPointer(new TYPE[size])

templateclass TEnableSharedFromThis
{
public:
    static boost::shared_ptrChangeToSharedPointer(TYPE* new_obj)
    {
        boost::shared_ptrp(new_obj);
        boost::weak_ptr *wp = (boost::weak_ptr *) & new_obj->weak_this_;
        if (new_obj->weak_this_.expired())
            new_obj->weak_this_ = p;
        return p;
    }
};

#define ENABLE_SHARED_FROM_THIS(TYPE) \
private:
    boost::shared_ptr shared_from_this()
    {
        boost::weak_ptr* wp = (boost::weak_ptr*) &weak_this_;
        return boost::shared_ptr(*wp);
    }
    boost::shared_ptr shared_from_this() const
    {
        boost::weak_ptr* wp = (boost::weak_ptr*) &weak_this_;
        return boost::shared_ptr(*wp);
    }

#define BASE_ENABLE_SHARED_FROM_THIS(TYPE)
public:
    boost::weak_ptr weak_this_;
private:
    boost::shared_ptr shared_from_this()
    {
        boost::weak_ptr* wp = (boost::weak_ptr*) &weak_this_;
        return boost::shared_ptr(*wp);
    }
    boost::shared_ptr shared_from_this() const
    {
        boost::weak_ptr* wp = (boost::weak_ptr*) &weak_this_;
        return boost::shared_ptr(*wp);
    }
protected:
    template 
    boost::shared_ptr shared_from_this(SUBTYPE* p = NULL)
    {
        return boost::shared_ptr(*(boost::weak_ptr*)&weak_this_);
    }
    template 
    boost::shared_ptr shared_from_this(SUBTYPE* p = NULL) const
    {
        return boost::shared_ptr(*(boost::weak_ptr*)&weak_this_);
    }


BASE_ENABLE_SHARED_FROM_THIS과 ENABLE_SHARED_FROM_THIS
이 2개의 매크로를 사용하는 것은 똑같습니다.
그러나 이제 new를 사용하면 안됩니다.
대신 만든 NEW 매크로를 사용해서 인스턴스를 생성해야만 합니다.
NEW매크로는 인스턴스 안의 weak_this_ 멤버변수 초기화도 함께 합니다.
NEW매크로만 써주시면 enable_shared_from_this 를 사용하는 것과
똑같은 수준으로 shared_ptr 관리가 됩니다.

단 주의하실 것이 있습니다.
첫번째 구현방법은 초기화가 자동이기 때문에 new로 만들지 않은 객체에서도
shared_from_this()를 사용할 수 있었습니다. 그러나 두번째 구현방법은
new로 만든 객체만 shared_from_this()를 쓸 수 있습니다.
제가 만든 것뿐만 아니라 boost 라이브러리가 제공하는 enable_shared_from_this
역시 new로 만든 객체에서만 shared_from_this()를 사용할 수 있는건 마찬가지입니다.
제가 못해서 그런게 아니니까 이해해 주시길...

shared_ptr은 정말 좋은 클래스입니다.
이걸 본격적으로 쓰면 C++로도 자바처럼 메모리에 대해 신경 안쓰고 코딩하는게
완전히 가능하겠더군요.
여러분도 shared_ptr의 세계로 빠져들어보세요.
오랫만에 c++에 푹 빠져봤습니다. 맨날 c만 하다가 c++ 코딩 본격적으로 다시 해본게
7년? 8년만이군요..
그럼 수고하세요.
Lyn [tohnokanna]   2010-03-22 01:00 X
잘 봤습니다. 그런데 다른거 하나를 얘기 하자면..

TYPE* p = new TYPE; 
shared_ptr<type> a = p; 
shared_ptr<type> b = p; 

애초에 이 코드는 컴파일이 안될겁니다만 =_=;;;

+ -

관련 글 리스트
206 boost의 shared_ptr에서 enable_shared_from_this를 못쓰는 경우 helloman 25600 2010/03/22
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.