C++ 다중 인터페이스 상속

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

Moderator: 류광

Locked
bequietzero
Posts: 5
Joined: 2006-06-19 22:40
Location: 인제대학교
Contact:

C++ 다중 인터페이스 상속

Post by bequietzero »

소스코드 다운

http://www.anifamily.com/file/GDBM(2).zip

안녕하세요 C++로 프로그램 만들어 보다가 잘 안되는 부분이 있어서 올려봅니다.
사실 학생이라 C++을 배우는 입장에서 아직도 잘 모르는 부분이 많습니다. 너그러이 봐주시기 바랍니다.

"다중 인터페이스 상속" 인데요..
인터페이스 라는 용어를 쓰기도 좀 뭐한 것 같습니다.
그냥 추상 클래스라고 하겠습니다. 클래스 내에 추상 메서드만 있는것은 아니고,
구현된 함수 내부도 있기 때문에 추상 클래스라고 하겠습니다.


처음에는 하나의 추상 클래스에 데이터베이스에 질의 하는 부분과, 데이터베이스 접속 하는 부분이 둘다 포함이 되어 있었으며
하위 클래스에서는 추상 클래스의 가상 함수 부분만 구현 해주면 여러 종류의 하위클래스를 만들수 있도록 만들었습니다.


그러나 데이터베이스 접속 하는 부분과, 데이터 베이스 질의 하는 부분을 분리 하고 싶었습니다.
데이터 베이스 접속할 때는 데이터 베이스 접속 인터페이스를 사용하고,
데이터베이스에 질의를 날릴때는 질의 관련 인터페이스를 사용하려고 생각하고 분리를 했습니다.

그러기 위해서는 클래스를 사용하는 곳에서는 두개의 인터페이스를 알고 있어야 할 것입니다.

다중 인터페이스를 상속해서 두 개의 인터페이스를 번갈아 가면서 사용하려고 했었습니다.
데이터베이스 접속 할때에는 접속 관련 인터페이스를 사용하고
질의를 할 때에는 질의 관련 인터페이스를 사용하는 방식으로요...


이렇게 하다보니까 문제가 생긴 겁니다.
저는 아직 프로그래밍 초보 딱지도 못때었지만....
여러 선배님들 조언 부탁 드립니다.


이 프로그램은 어떤 구조를 가져야 잘된 프로그램 일까요???
Last edited by bequietzero on 2006-06-19 23:01, edited 1 time in total.
bequietzero
Posts: 5
Joined: 2006-06-19 22:40
Location: 인제대학교
Contact:

Post by bequietzero »

참고로 간단하게 예를 들자면 아래와 같은 부분에서 애러가 납니다.

Code: Select all

 #include <vector>
using namespace std; 

struct CData  
{
    void* point;
    long longdata;
    long longdata2;
};

class CInterface1  
{
public:
    CInterface1(){}
    virtual ~CInterface1(){} 

public:
    vector<CData> data;

};

 

class CInterface2  
{
public:
    CInterface2(){}
    virtual ~CInterface2(){}
    virtual void adddata()=0;
};

 

class CChild : public CInterface1, CInterface2  
{
public:
    CChild() {}
    virtual ~CChild(){}
    void adddata() {

        CData data2;
        data.push_back(data2);
        data.push_back(data2);
        data.push_back(data2);
        data.push_back(data2);

    }
};

 

int main(int argc, char* argv[])
{

    CInterface1* parent = new CChild();              
    CInterface2* parent2 = (CInterface2*)parent;   -------- 이부분
    parent2->adddata();                                        -------- 이부분
    return 0;

}
문제가 되는 부분은 어디인지 확실히 알겠습니다.

그러나 이 구조를 어떻게 변경해야지 원활히 두개의 인터페이스를 자유자제로 사용 할 수 있을까요??
Last edited by bequietzero on 2006-06-20 01:06, edited 1 time in total.
chyaya
Posts: 4
Joined: 2006-06-19 23:03
Contact:

다중 상속 문법

Post by chyaya »

Code: Select all

class CChild : public CInterface1, CInterface2  
{
}
클래스의 선언부분에서 위와 같이 상속을 하게 되면

CInterface1은 public 상속이 되고

Cinterface2는 private상속이 되게됩니다.

그 이유는 상속의 디폴트가 private이기 때문입니다.

따라서 여기서 CInterface2 클래스를 private로 상속하였기 때문에

has-a 관계가 되어 CChild 클래스의 포인터를 CInterface2 포인터로 치환할 수 없습니다.

Code: Select all

class CChild : public CInterface1, public CInterface2  
{
}
흔히 변수의 선언과 혼동하기가 쉽습니다만

이 경우에는 위와 같이 명시적으로 public 키워드를 써주셔야합니다.

링크하신 소스에서도 CDBConnectorODBC 클래스의 상속 부분을

위와 같은 방법으로 고쳐주니 잘 컴파일이 됩니다.

Code: Select all

int main(int argc, char* argv[])
{

    CInterface1* parent = new CChild();              
    CInterface2* parent2 = (CInterface2*)parent;   -------- 이부분
    parent2->adddata();                                        -------- 이부분
    return 0;

}
그리고 위 부분은 위에 클래스 상속 문제를 떠나 에러없이 잘 컴파일 됩니다. (VS 6.0, VS.net 2003)

부족한 답변이나마 도움이 되었으면 좋네요 :oops:
bequietzero
Posts: 5
Joined: 2006-06-19 22:40
Location: 인제대학교
Contact:

답변 감사드립니다.

Post by bequietzero »

답변 감사드립니다.

private이군요...

그런대 그부분에서 애러나기 전에 public 으로 선언 했었습니다.

그러나 케스팅 에서는 애러가 나지 않아도...

예를 들면

Code: Select all

    inline bool
    IQuery::executeQuery(string& statement, QUERY_FORMAT format, vector<QueryData>& refData) {

        m_enQueryFormat = format;
        m_strQueryStatement = statement;              +---------------- 이부분
        m_vRefData = refData;
        

        switch(m_enQueryFormat) {
            case QUERY_SELECT: return executeSELECT();
            case QUERY_INSERT: return executeINSERT();
            case QUERY_UPDATE: return executeUPDATE();
            case QUERY_DELETE: return executeDELETE();
            default: return false;
        }
    }
이부분 같은 경우에는 애러가 납니다.

IDBConnector의 인터페이스 포인터로 가지고 있던 것을 IQuery*로 형변환 하면 잘되기는 합니다만..
위와 같이 IQuery 추상 클래스에 있는 멤버에 가변형 변수를 넣을시에 메모리 참조 오류가 나게 됩니다.


이부분이 계속 막히는 부분인데요...
제가 구조를 잘못 짜서 그런거 같기도 하고요...

구조를 잘못 짰으면 어떤식으로 짜면 더 좋을지 조언 부탁 드립니다.
chyaya
Posts: 4
Joined: 2006-06-19 23:03
Contact:

Post by chyaya »

음. 정확히 프로그램을 테스트 해보진 못했습니다만

이런 문제가 아닐까 싶습니다.

Code: Select all

class CBase
{
};

class CChild : public CBase
{
};
위와 같은 일반적인 상속의 경우 CChild*를 CBase* 타입으로 치환 가능한 것은

상속된 class의 경우 메모리에

-------------- <- 여기가 포인터가 가르키는 부분
CBase
--------------
CChild
--------------

이와 같은 구조로 들어가 있기때문입니다.

그러나

Code: Select all

class CBase1
{
};

class CBase2
{
};

class CChild : public CBase1, public CBase2
{
};
위와 같은 경우에서 CChild의 메모리 구조는

-------------- <- 여기가 포인터가 가르키는 부분
CBase1
--------------
CBase2
--------------
CChild
--------------

이런 식으로 됩니다. 따라서 CChild*를 CBase2* 타입에 대입하는 경우는

정의되어있지 않습니다.

프로그램의 구조적인 부분은 제가 유사한 프로그램을 만들어 본적이 없어서

답변 못 드리는 점 이해해주시기바랍니다 :cry:
웃음, 꿈, 어리석음
적어도 이 세 개만큼은 컴퓨터한테 안 집니다. +_+
bequietzero
Posts: 5
Joined: 2006-06-19 22:40
Location: 인제대학교
Contact:

일단 되는 호출을 알려 드리겠습니다.

Post by bequietzero »

제가 테스트를 해봤었는데요..

위처럼 클래스 구조를 짰을때요..

Code: Select all

int main(int argc, char* argv[])
{

    CChild* child = new CChild();  
    CBase1* base1 = (CBase1*)child;              
    CBase2* base2 = (CBase2*)child; 
    base2->adddata();                                      
    return 0;

}
위와 같이 하면 호출이 잘 이루어 집니다.


그러나 사용하는 입장에서 하위클래스를 알면 안되기 때문에
프로그래밍 하다가 보니까 문제가 생겼습니다.
home page | http://www.bequietzero.com
e-mail | kevinpark1981 <at> gmail.com
소속 | 인제대학교 전산학과 MITL

-大丈夫-
居天下之廣居
立天下之正位
行天下之大道
得志與民由之
不得志獨行其道
富貴不能淫
貧賤不能移
威武不能屈
此之謂大丈夫
sequoia
Posts: 76
Joined: 2005-11-02 11:27

C스타일 캐스트는 쓰지 마세요.

Post by sequoia »

Code: Select all

 int main(int argc, char* argv[])
{

    CInterface1* parent = new CChild();              
    CInterface2* parent2 = (CInterface2*)parent;   -------- 이부분
    parent2->adddata();                                        -------- 이부분
    return 0;

}

parent2 = dynamic_cast<CInterface2>(parent);

와 같이 사용하세요. 저 경우 아마도 컴파일러가 CInterface1과 2의 관계를 인식하지 못하고 reinterpret_cast로 처리해버릴 가능성이 높아보입니다. 클래스 상속 구조를 사용하는 프로그램에서 C스타일 캐스트는 완전 독입니다. 자신이 어떤 캐스트를 사용하는 것인지 명확히 알고 해당되는 캐스트를 직접 가져다 쓰세요. (다중 상속을 실무에서 써본 적이 거의 없어서 이경우 dynamic_cast가 잘 작동할지 모르겠네요. -_-)

둘째로, 정말 인터페이스 클래스라서 다중 상속을 사용하신다면 인터페이스 클래스 자체에는 멤버 변수를 갖지 않게 하는 것이 좋습니다.

셋째로, 위와 같이 콘크리트 클래스 인스턴스->인터페이스 타입 변환이 아니라 인터페이스->다른 인터페이스 타입 변환이 필요한 경우는 나오지 않는 것이 좋습니다. 저런게 필요한 상황이 나온다면, 즉 인터페이스간의 커플링이 발생한다는 것은 인터페이스의 설계가 더 정제될 가능성이 남아있다는 뜻이죠. 하나의 상황에서는 하나의 인터페이스만 필요하도록 하고 하나의 인터페이스가 다른 인터페이스와 항상 함께 사용된다는 가정이 들어간 코드는 작성하지 않도록 하는 것이 좋습니다. 위 코드는 CInterface1의 인터페이스를 가진 개체는 항상 CInterface2의 인터페이스도 갖는다는 가정하에 작성된 코드로군요.
gimmesilver
Posts: 85
Joined: 2005-10-23 05:46
Location: NCsoft openmaru studio
Contact:

잘못된 캡슐화 구조입니다...

Post by gimmesilver »

우선...sequoia님이 말씀하신 dynamic_cast<>는 안됩니다. parent1과 parent2는 상속관계가 아니기 때문입니다.
그리고 최초 질문하신 분은 왜 두 인터페이스를 분리하고 서로 숨기려고 하는지 모르겠군요...
객체 지향 프로그래밍에서 숨겨야 할 것은 '구현 내용'이지 '인터페이스'가 아닙니다...
즉, 숨겨야 할 부분은 데이터베이스 접속 하는 부분과, 데이터 베이스 질의 하는 부분의 세부 구현 내용이지 접속에 필요한 인터페이스와 질의 인터페이스가 아니라는 뜻입니다.
따라서 질문하신 그런 방식은 바람직하지 않습니다.
결국 잘못된 것은 child class의 구조가 아니라 호출(사용)하는 부분에 있습니다.
비회원

Re: 잘못된 캡슐화 구조입니다...

Post by 비회원 »

gimmesilver wrote:우선...sequoia님이 말씀하신 dynamic_cast<>는 안됩니다. parent1과 parent2는 상속관계가 아니기 때문입니다.
됩니다. parent1의 동적 타입과 parent2의 타입이 상속관계이기 때문입니다.
바람직한 구조가 아니라는 데는 동의합니다.
dyks
Posts: 79
Joined: 2004-09-23 14:41
Location: Andromeda Galaxy

Re: 잘못된 캡슐화 구조입니다...

Post by dyks »

비회원 wrote:
gimmesilver wrote:우선...sequoia님이 말씀하신 dynamic_cast<>는 안됩니다. parent1과 parent2는 상속관계가 아니기 때문입니다.
됩니다. parent1의 동적 타입과 parent2의 타입이 상속관계이기 때문입니다.
바람직한 구조가 아니라는 데는 동의합니다.
dynamic_cast가 해당 포인터가 가르키는 객체의 실제 타입을 검사하는 건 사실이지만, 하이라키를 무시하고 캐스팅 하면 알아서 해주는 수준은 아닙니다. 아마 컴파일 에러를 내고 죽을겁니다.
비회원

Re: 잘못된 캡슐화 구조입니다...

Post by 비회원 »

dyks wrote:
비회원 wrote:
gimmesilver wrote:우선...sequoia님이 말씀하신 dynamic_cast<>는 안됩니다. parent1과 parent2는 상속관계가 아니기 때문입니다.
됩니다. parent1의 동적 타입과 parent2의 타입이 상속관계이기 때문입니다.
바람직한 구조가 아니라는 데는 동의합니다.
dynamic_cast가 해당 포인터가 가르키는 객체의 실제 타입을 검사하는 건 사실이지만, 하이라키를 무시하고 캐스팅 하면 알아서 해주는 수준은 아닙니다. 아마 컴파일 에러를 내고 죽을겁니다.
직접 해보시지요.
컴파일뿐만 아니라 실행까지 잘 됩니다.
이것은 계층구조를 무시하는 것이 아닙니다.

Code: Select all

    CChild* parent = new CChild();              
    CInterface2* parent2 = dynamic_cast<CInterface2>(parent);
이것과 차이가 있을 거라고 생각하십니까?
nglifejoe
Posts: 50
Joined: 2005-03-08 17:42

이러한 문제라면..

Post by nglifejoe »

Code: Select all

Interface1* pParent = new Child;
Interface2* pParent2 = dynamic_cast<Interface2>( static_cast<Child>(pParent) );
Child -> Interface1 -> Child -> Interface2

이렇게 해주어야 하지 않을까요?
Interface1과 Interface2 사이의 공통점이라면.. Child의 Base라는 것이겠지요..
그러니.. Child를 통하게 되면.. 안전한 형변환이 가능하게 됩니다..

그리고.. Interface1* 에서 Interface2*의 형태로 오류없는 컴파일이 된다고 해서..
계층구조를 무시하지 않는다고는 말할 수 없을것 같습니다..
Interface1과 Interface2는 전혀 아무런 관계도 없는 남남이니까..
이 둘간에 이루어지는 형변환은 강제형변환입니다..

즉, signedInt와 unsignedInt 사이에서의 형변환..
int와 float사이의 형변환과 같다고 생각됩니다..
이런 형태의 형변환은 계층구조를 지킨다고 할수없습니다..
sequoia
Posts: 76
Joined: 2005-11-02 11:27

Re: 이러한 문제라면..

Post by sequoia »

djaakpig wrote:

Code: Select all

Interface1* pParent = new Child;
Interface2* pParent2 = dynamic_cast<Interface2>( static_cast<Child>(pParent) );
Child -> Interface1 -> Child -> Interface2

이렇게 해주어야 하지 않을까요?
Interface1과 Interface2 사이의 공통점이라면.. Child의 Base라는 것이겠지요..
그러니.. Child를 통하게 되면.. 안전한 형변환이 가능하게 됩니다..

그리고.. Interface1* 에서 Interface2*의 형태로 오류없이 잘 된다고 해서..
계층구조를 무시하지 않는다고는 말할 수 없을것 같습니다..
Interface1과 Interface2는 전혀 아무런 관계도 없는 남남이니까..
이 둘간에 이루어지는 형변환은 강제형변환입니다..

즉, signedInt와 unsignedInt 사이에서의 형변환..
int와 float사이의 형변환과 같다고 생각됩니다..
이런 형태의 형변환은 계층구조를 지킨다고 할수없습니다..
dynamic_cast는 기본적으로 '런타임'에 해당 객체의 타입 정보를 보고 동적으로 캐스팅하는 것이며,
해당 객체가 런타임에 해당 타입으로 변환될 수 있다면 변환해줍니다.

만약 말씀하신대로 dynamic_cast가 작동한다면 저런 용법은 컴파일 타임에 에러를 내도록 되겠지요. 왜냐하면 상속 구조는 컴파일 타임에 명백하게 알 수 있고, 저런 용법이 런타임 조건에 관계없이 항상 실패해야 한다면 컴파일이 되도록 놔둘 이유가 없으니까요.

다중 상속을 잘 안써서 저런 용법을 흔히 볼 수 없긴 하지만, 원칙적으로는 저런 용법도 정상 작동하는 것이 맞을겁니다. 그래서 체크 없이 그냥 올렸고요. :-) 다이나믹 캐스트가 보통 자식 클래스로 변환할때 많이 쓰긴 하지만 정확히 말하면 '컴파일 타임 타입과 관계없이 런타임에 알 수 있는 타입정보로 변환'하는거니까요.

어쨌든 저런 표현을 사용할 필요가 있게 만드는 설계 자체가 좀 수정될 필요가 있어보이긴 합니다만, 어쨌든 dynamic_cast를 저런 식으로 사용하는 것은 틀린 사용법이 아니랍니다.
nglifejoe
Posts: 50
Joined: 2005-03-08 17:42

Interface1 -> Interface2의 형변환은..

Post by nglifejoe »

아래와 같은 C형태의 변환은.. vtable 정보도 깨져버릴거 같은데요..

Code: Select all


class Interface1
{
public:
  Interface1() {}
  virtual ~Interface1() { } 
};

class Interface2  
{
public:
  Interface2(){}
  virtual ~Interface2() { }

  virtual void addData() = 0;
};

class Child: public Interface1, public Interface2
{
public:
  Child() {}
  virtual ~Child() { }

  void addData() { std::cout << "Child::addData"; }
};


main()
{
    CInterface1* parent = new CChild();              
    CInterface2* parent2 = (CInterface2*)parent;
    parent2->adddata();  //AccessViolation
}
/GR 옵션 켜고.. 테스트해봤는데..
위와 같은 경우.. vtable이 깨져버립니다..

[sequoia] 님께서 올리신 글이 잘 이해가 되지 않습니다..
dynamic_cast<>(static_cast<>()) 이 그냥 dynamic_cast<dynamic>
즉, /GR옵션을 끄고 컴파일하였을 때 .NET 7.0이 다음과 같이 알려줍니다..

Code: Select all

Main.cpp(57) : warning C4541: 'dynamic_cast'이(가) /GR 스위치와 함께 다형 형식 'Interface1'에 사용되었습니다. 예기치 않은 결과가 발생할 수 있습니다.
Last edited by nglifejoe on 2006-06-20 14:15, edited 4 times in total.
sequoia
Posts: 76
Joined: 2005-11-02 11:27

Re: Interface1 -> Interface2의 형변환은..

Post by sequoia »

djaakpig wrote:vtable 정보도 깨져버릴거 같은데요..

Code: Select all


class Interface1
{
public:
  Interface1() {}
  virtual ~Interface1() { } 
};

class Interface2  
{
public:
  Interface2(){}
  virtual ~Interface2() { }

  virtual void addData() = 0;
};

class Child: public Interface1, public Interface2
{
public:
  Child() {}
  virtual ~Child() { }

  void addData() { std::cout << "Child::addData"; }
};


main()
{
    CInterface1* parent = new CChild();              
    CInterface2* parent2 = (CInterface2*)parent;
    parent2->adddata();  //AccessViolation
}
/GR 옵션 켜고.. 테스트해봤는데..
위와 같은 경우.. vtable이 깨져버립니다..

[sequoia] 님께서 올리신 글이 잘 이해가 되지 않습니다..
dynamic( static() ) 이 그냥 dynamic이나 다를게 없다는 말씀으로 이해하고 있습니다.. ^^;;
다만.. dynamic만 사용할 경우.. /GR 옵션을 꺼주게 되면..
역시나.. 런타임오류 발생하는걸로 알고 있습니다.. (-- )>
/GR이 뭔지 모르겠습니다만 위 소스에선 dynamic_cast가 아니라 C스타일 캐스트를 사용했네요.
제 예상에 GR이 '런타임 타입 정보 사용'에 대한 옵션이라면 저런 dynamic_cast뿐 아니라 모든 dynamic_cast를 사용할 수 없게 될 겁니다. 그건 dynamic_cast 표준이랑은 상관없죠.
nglifejoe
Posts: 50
Joined: 2005-03-08 17:42

Re: Interface1 -> Interface2의 형변환은..

Post by nglifejoe »

제가 올린.. c타입의 형변환은 어느분이 실행이 된다기에.. 않된다는걸 말씀드릴려고 올린 코드입니다..
( 글 내용이 좀 이상했군요.. 올린 글 내용 수정했습니다.. ㅠ_ㅜ )

그리고..
/GR옵션 끄더라도.. 1단계 상위 캐스팅은 가능한걸로 알고 있습니다.. ( 책을 뒤져보긴.. ㅜ_ㅡ )
한번 테스트를 해보시기 바랍니다.. ㅠ_ㅜ

Interface1, Interface2는 Child의 1단계 상위니까..
/GR옵션에 의한 복잡한 계층정보 없이도.. dynamic과 static을 사용해서 안전하게 변환이 가능합니다..

interface1에서 interface2의 dynamic변환은 1단계변환으로는 해결을 할 수 없지 않습니까?
논리적으로 따지더라도.. 둘간의 연관성이 없기때문에.. Child를 경유해서 변환할 수밖에 없습니다..
gimmesilver
Posts: 85
Joined: 2005-10-23 05:46
Location: NCsoft openmaru studio
Contact:

Re: 이러한 문제라면..

Post by gimmesilver »

sequoia wrote:
djaakpig wrote:

Code: Select all

Interface1* pParent = new Child;
Interface2* pParent2 = dynamic_cast<Interface2>( static_cast<Child>(pParent) );
Child -> Interface1 -> Child -> Interface2

이렇게 해주어야 하지 않을까요?
Interface1과 Interface2 사이의 공통점이라면.. Child의 Base라는 것이겠지요..
그러니.. Child를 통하게 되면.. 안전한 형변환이 가능하게 됩니다..

그리고.. Interface1* 에서 Interface2*의 형태로 오류없이 잘 된다고 해서..
계층구조를 무시하지 않는다고는 말할 수 없을것 같습니다..
Interface1과 Interface2는 전혀 아무런 관계도 없는 남남이니까..
이 둘간에 이루어지는 형변환은 강제형변환입니다..

즉, signedInt와 unsignedInt 사이에서의 형변환..
int와 float사이의 형변환과 같다고 생각됩니다..
이런 형태의 형변환은 계층구조를 지킨다고 할수없습니다..
dynamic_cast는 기본적으로 '런타임'에 해당 객체의 타입 정보를 보고 동적으로 캐스팅하는 것이며,
해당 객체가 런타임에 해당 타입으로 변환될 수 있다면 변환해줍니다.

만약 말씀하신대로 dynamic_cast가 작동한다면 저런 용법은 컴파일 타임에 에러를 내도록 되겠지요. 왜냐하면 상속 구조는 컴파일 타임에 명백하게 알 수 있고, 저런 용법이 런타임 조건에 관계없이 항상 실패해야 한다면 컴파일이 되도록 놔둘 이유가 없으니까요.

다중 상속을 잘 안써서 저런 용법을 흔히 볼 수 없긴 하지만, 원칙적으로는 저런 용법도 정상 작동하는 것이 맞을겁니다. 그래서 체크 없이 그냥 올렸고요. :-) 다이나믹 캐스트가 보통 자식 클래스로 변환할때 많이 쓰긴 하지만 정확히 말하면 '컴파일 타임 타입과 관계없이 런타임에 알 수 있는 타입정보로 변환'하는거니까요.

어쨌든 저런 표현을 사용할 필요가 있게 만드는 설계 자체가 좀 수정될 필요가 있어보이긴 합니다만, 어쨌든 dynamic_cast를 저런 식으로 사용하는 것은 틀린 사용법이 아니랍니다.
sequoia님이 맞습니다. 제가 잘못 알고 있었군요...
C++ 표준 5.2.7에 dynamic_cast<>의 run time check 기준이 아래와 같이 설명되어 있습니다.
- If, in the most derived object pointed(referred) to by v, v points (refers) to a public base class sub-object of a T object, and if only one object of type T is derived from the sub-object pointed(refferd) to by v, the result is a pointer (an lvalue referring) to that T object.
- Otherwise, if v points(refers) to a public base class sub-object of the most derived object, and the type of the most derived object has an unambiguous public base class of type T, the result is a pointer (an lvalue referring) to the T sub-object of the most derived object.
- Otherwise, the run-time check fails
의역하자면 아래와 같습니다.
dynamic<T>(v)일 때,
- 만약 v를 참조하는 최하위 상속 객체에서 봤을 때, v가 T의 public 상위 객체 타입이거나, 그 반대의 경우에 결과는 T의 pointer 혹은 참조 객체이다.
- 또는 만약 v가 최하위 상속 객체의 public 상위 객체이고 T도 최하위 상속 객체의 모호하지 않은 public 상위 객체인 경우 결과는 최하위 상속 객체의 T 객체에 대한 pointer 혹은 참조 객체이다.
- 위 두 조건을 만족하지 못하면 실행 시간 체크는 실패한다.

따라서 dynamic_cast<>는 직접적인 상속 구조가 아니더라도 - 두 번째 조건에 의해 - 사용이 가능합니다.
nglifejoe
Posts: 50
Joined: 2005-03-08 17:42

Re: 이러한 문제라면..

Post by nglifejoe »

gimmesilver wrote:
sequoia wrote:
djaakpig wrote:

Code: Select all

Interface1* pParent = new Child;
Interface2* pParent2 = dynamic_cast<Interface2>( static_cast<Child>(pParent) );
Child -> Interface1 -> Child -> Interface2

이렇게 해주어야 하지 않을까요?
Interface1과 Interface2 사이의 공통점이라면.. Child의 Base라는 것이겠지요..
그러니.. Child를 통하게 되면.. 안전한 형변환이 가능하게 됩니다..

그리고.. Interface1* 에서 Interface2*의 형태로 오류없이 잘 된다고 해서..
계층구조를 무시하지 않는다고는 말할 수 없을것 같습니다..
Interface1과 Interface2는 전혀 아무런 관계도 없는 남남이니까..
이 둘간에 이루어지는 형변환은 강제형변환입니다..

즉, signedInt와 unsignedInt 사이에서의 형변환..
int와 float사이의 형변환과 같다고 생각됩니다..
이런 형태의 형변환은 계층구조를 지킨다고 할수없습니다..
dynamic_cast는 기본적으로 '런타임'에 해당 객체의 타입 정보를 보고 동적으로 캐스팅하는 것이며,
해당 객체가 런타임에 해당 타입으로 변환될 수 있다면 변환해줍니다.

만약 말씀하신대로 dynamic_cast가 작동한다면 저런 용법은 컴파일 타임에 에러를 내도록 되겠지요. 왜냐하면 상속 구조는 컴파일 타임에 명백하게 알 수 있고, 저런 용법이 런타임 조건에 관계없이 항상 실패해야 한다면 컴파일이 되도록 놔둘 이유가 없으니까요.

다중 상속을 잘 안써서 저런 용법을 흔히 볼 수 없긴 하지만, 원칙적으로는 저런 용법도 정상 작동하는 것이 맞을겁니다. 그래서 체크 없이 그냥 올렸고요. :-) 다이나믹 캐스트가 보통 자식 클래스로 변환할때 많이 쓰긴 하지만 정확히 말하면 '컴파일 타임 타입과 관계없이 런타임에 알 수 있는 타입정보로 변환'하는거니까요.

어쨌든 저런 표현을 사용할 필요가 있게 만드는 설계 자체가 좀 수정될 필요가 있어보이긴 합니다만, 어쨌든 dynamic_cast를 저런 식으로 사용하는 것은 틀린 사용법이 아니랍니다.
sequoia님이 맞습니다. 제가 잘못 알고 있었군요...
C++ 표준 5.2.7에 dynamic_cast<>의 run time check 기준이 아래와 같이 설명되어 있습니다.
- If, in the most derived object pointed(referred) to by v, v points (refers) to a public base class sub-object of a T object, and if only one object of type T is derived from the sub-object pointed(refferd) to by v, the result is a pointer (an lvalue referring) to that T object.
- Otherwise, if v points(refers) to a public base class sub-object of the most derived object, and the type of the most derived object has an unambiguous public base class of type T, the result is a pointer (an lvalue referring) to the T sub-object of the most derived object.
- Otherwise, the run-time check fails
의역하자면 아래와 같습니다.
dynamic<T>(v)일 때,
- 만약 v를 참조하는 최하위 상속 객체에서 봤을 때, v가 T의 public 상위 객체 타입이거나, 그 반대의 경우에 결과는 T의 pointer 혹은 참조 객체이다.
- 또는 만약 v가 최하위 상속 객체의 public 상위 객체이고 T도 최하위 상속 객체의 모호하지 않은 public 상위 객체인 경우 결과는 최하위 상속 객체의 T 객체에 대한 pointer 혹은 참조 객체이다.
- 위 두 조건을 만족하지 못하면 실행 시간 체크는 실패한다.

따라서 dynamic_cast<>는 직접적인 상속 구조가 아니더라도 - 두 번째 조건에 의해 - 사용이 가능합니다.
좋은 내용감사합니다.. ^^;
다만 위의 두번째 조건을 가능하게 해주는게.. RTTI정보를 생성해주는 /GR옵션입니다..
이 /GR옵션이 꺼져있다면.. 계층구조를 분석할 정보가 없기 때문에..
위의 두번째 조건에 의한 dynamic_cast도 실패하게 됩니다..
dynamic_cast<>(static_cast<>())를 사용하게 되면.. 위의 첫번째 조건에 만족하게 되는거군요.. ^^;

P.S 근데 잘못 인용하신거 아닌가요? ^^;;
gimmesilver
Posts: 85
Joined: 2005-10-23 05:46
Location: NCsoft openmaru studio
Contact:

Re: 이러한 문제라면..

Post by gimmesilver »

djaakpig wrote: 좋은 내용감사합니다.. ^^;
다만 위의 두번째 조건을 가능하게 해주는게.. RTTI정보를 생성해주는 /GR옵션입니다..
이 /GR옵션이 꺼져있다면.. 계층구조를 분석할 정보가 없기 때문에..
위의 두번째 조건에 의한 dynamic_cast도 실패하게 됩니다..
dynamic_cast<>(static_cast<>())를 사용하게 되면.. 위의 첫번째 조건에 만족하게 되는거군요.. ^^;

P.S 근데 잘못 인용하신거 아닌가요? ^^;;
djaakpig님께서 말씀하시는 /GR 옵션이 꺼져 있어도 dynamic casting이 된다는 말씀의 의미가

1) RTTI 옵션이 없더라도 컴파일러가 기본적인 child <-> parent 관계의 dynamic casting을 지원한다는 의미인지 아니면
2) RTTI 옵션이 없더라도 static casting 만으로 child <-> parent 간의 casting이 된다는 의미인지 모르겠습니다.

첫 번째 말씀라면 그것은 특정 컴파일러에 의존한 이야기이므로 올바른 해결책이 아닙니다.

만약 두 번째 말씀이라면 그건 잘못된 생각입니다.
단지 해당 컴파일러에서 상속 관계 메모리 구조가 우연히 static casting에서도 맞아 떨어졌을 뿐이지 표준에서 정의하는 엄밀한 의미의 dynamic casting이 된 것이 아닙니다. 따라서 이것이 항상 잘 될것이라는 보장이 없습니다.

물론 djaakpig님께서 언급하신 dynamic_cast<>(static_cast<>()) 이런 방식 역시 올바른 dynamic casting이 아닙니다.

마지막으로 제가 표준을 인용한 이유는 이 스레드의 윗 부분에서 제가 상속 관계에서 직접적인 상속 관계에 있지 않으면 상위 객체 간의 캐스팅이 되지 않을 것이라고 말한 제 주장이 틀렸다는 것을 언급하고자 했기 때문입니다.

p.s. 스레드가 다소 엉뚱한 방향으로 흘러간 것 같은데 어쨌든 상위 객체 간의 dynamic casting이 문법적으로 된다고 하더라도 그런 구조는 결코 좋은 구조가 아니라고 생각합니다.
dyks
Posts: 79
Joined: 2004-09-23 14:41
Location: Andromeda Galaxy

Re: 이러한 문제라면..

Post by dyks »

gimmesilver wrote:
djaakpig wrote: 좋은 내용감사합니다.. ^^;
다만 위의 두번째 조건을 가능하게 해주는게.. RTTI정보를 생성해주는 /GR옵션입니다..
이 /GR옵션이 꺼져있다면.. 계층구조를 분석할 정보가 없기 때문에..
위의 두번째 조건에 의한 dynamic_cast도 실패하게 됩니다..
dynamic_cast<>(static_cast<>())를 사용하게 되면.. 위의 첫번째 조건에 만족하게 되는거군요.. ^^;

P.S 근데 잘못 인용하신거 아닌가요? ^^;;
djaakpig님께서 말씀하시는 /GR 옵션이 꺼져 있어도 dynamic casting이 된다는 말씀의 의미가

1) RTTI 옵션이 없더라도 컴파일러가 기본적인 child <-> parent 관계의 dynamic casting을 지원한다는 의미인지 아니면
2) RTTI 옵션이 없더라도 static casting 만으로 child <-> parent 간의 casting이 된다는 의미인지 모르겠습니다.

첫 번째 말씀라면 그것은 특정 컴파일러에 의존한 이야기이므로 올바른 해결책이 아닙니다.

만약 두 번째 말씀이라면 그건 잘못된 생각입니다.
단지 해당 컴파일러에서 상속 관계 메모리 구조가 우연히 static casting에서도 맞아 떨어졌을 뿐이지 표준에서 정의하는 엄밀한 의미의 dynamic casting이 된 것이 아닙니다. 따라서 이것이 항상 잘 될것이라는 보장이 없습니다.

물론 djaakpig님께서 언급하신 dynamic_cast<>(static_cast<>()) 이런 방식 역시 올바른 dynamic casting이 아닙니다.

마지막으로 제가 표준을 인용한 이유는 이 스레드의 윗 부분에서 제가 상속 관계에서 직접적인 상속 관계에 있지 않으면 상위 객체 간의 캐스팅이 되지 않을 것이라고 말한 제 주장이 틀렸다는 것을 언급하고자 했기 때문입니다.

p.s. 스레드가 다소 엉뚱한 방향으로 흘러간 것 같은데 어쨌든 상위 객체 간의 dynamic casting이 문법적으로 된다고 하더라도 그런 구조는 결코 좋은 구조가 아니라고 생각합니다.
저도 잘못 알고 있었군요.^^(긁적;)
dynamic_cast의 경우 명확한 상속관계 없이도 사용가능하네요. 전 다만 static_cast의 RTTI 사용 버전 정도로 알고 있었는데.
(단정적인 논조의 반박성 리플을 단 것에 사과드립니다.)

한 수 배우고 갑니다.^^
Locked