2014-06-13 7 views
0

다음 코드가 있다고 말하면 안전할까요?파생 클래스에 캐스팅 된 기본 클래스의 함수를 호출하십시오.

기본 클래스 :

class B 
{ 
    public: 
    B(bool isDerived = false) : m_isDerived(isDerived) {} 
    bool isDerived() { return m_isDerived; } 

    private: 
    bool m_isDerived; 
} 

파생 클래스 :

class D : public B 
{ 
    public: 
    D() : B(true) {} 
} 

코드 :

B* b = new B(); // Create new base class 
D* unknown = static_cast<D*>(b); // Cast the base class into a derived class 

if (unknown->isDerived()) // Is this allowed? 
    // unknown is a D and can be used as such 
else 
    // unknown is not a D and can not be used 

내가 안전하게 호출 할 수 있습니다 unknown-> isDerived()도 알 수없는 생각되어 정말 B 이 경우에는?

우리는 알 수없는 NEVER에 B * 또는 D *가 포함되어 있지 않으며 isDerived()가 확인 될 때까지 알 수없는 것으로 처리하지 않는다고 가정합니다.

편집 :

내가이 작업을 수행하기 위해 노력하고있어 이유를 설명하려고합니다 질문을 감안할 때 : 그래서 기본적으로 나는 물론 직접 연결되지 않을 수있는 Windows 트리 제어 할 수 있습니다 C++ 트리 구조에 데이터를 저장하는 데 사용하고 있습니다. 그래서 나는 트리 컨트롤의 각 노드와 함께 저장되어있는 DWORD_PTR에 내 데이터를 reinterpret_cast해야만이 둘 사이의 연결이 생깁니다. 내 트리 구조는 기본 유형 (일반 노드) 또는 파생 유형 (다르게 처리되어야하는 더 많은 정보가있는 노드)으로 구성됩니다. 이것들에 대한 포인터는 reinterpret_cast : 트리 컨트롤에 넣고 넣는다.

트리 컨트롤을 통해 진행할 때, 파생 된 유형의 노드에 대해 조치를 취해야하므로 파생 된 유형으로 DWORD_PTR을 reinterpret_cast하고 싶습니다. 하지만 전적으로 올바른하려면 reinterpret_cast 기본 형식 (먼저 같아요?), 파생 된 형식이면 파생 된 형식으로 다운 캐스트합니다. 그러나 나는 그것을 파생 형식으로 즉시 reinterpret_cast하여 조금 더 간단하게 만들 수 있다고 생각하고 실제로 파생 형식 인 경우 함수를 통해 확인합니다. 그렇지 않다면 나는 더 이상 아무것도하지 않습니다. 내 마음에 포인터의 기본 클래스 데이터를 얼마나 많은 파생 된 상관없이 같은 메모리 위치에 있어야하지만 내가 왜 여기에 물어 잘못 될 수도 있습니다.

나는 거기에 윈도우를 포함하지 않음으로써 질문을 명확하게하고 싶어하지만, 현실에서 내가 원하는이 가까운 것이 없을 것입니다 : 기본적으로 상관없이 나는 그것이 일부에서 여전히 안전하지 않은 일을

B* b1 = new B(); 
B* b2 = new D(); 
DWORD_PTR ptr1 = reinterpret_cast<DWORD_PTR>(b1); 
DWORD_PTR ptr2 = reinterpret_cast<DWORD_PTR>(b2); 

D* unknown = reinterpret_cast<D*>(ptr1 /* or ptr2 */); // Safe? 
unknown->isDerived(); // Safe? 

데이터를 reinterpret_cast해야합니다.

+0

는 관련이없는 유형을 캐스팅'reinterpret_cast'를 사용하지할까요? – cppcoder

+0

@cppcoder 그건 강제 다운 캐스트이고 가능성이 미정의 동작을 방아쇠를 당길 것입니다 –

+0

컴파일을 시도하고 그것이 무엇을 참조하십시오? –

답변

1

unknown-> isDerived()를이 경우 실제로 B라고해도 안전하게 호출 할 수 있습니까?

우선이 작업을 수행하는 이유는 무엇입니까? b->isDerived()으로 전화를 걸어 다운 캐스팅을 할 수 있습니다.

조기 및 가능성이있는 다운 캐스트는 정의되지 않은 동작을 산출하지만 (이 경우 보편적으로 경멸을 받아야합니다)이 경우 작동해야합니다. B와 D 중 어느 것도 상대 오프셋을 변경할 수있는 암시 적 데이터 멤버가 없습니다 m_isDerivedisDerived 멤버 함수의 주소는 일정합니다.

그래, 그것 해야합니다. 으로 충분해야합니다.

편집 :

#include <cstddef> // for offsetof macro 
#include <cassert> // for assert 

#define offsetofclass(base, derived) ((static_cast<base*>((derived*)8))-8) 

class Test 
{ 
public: 
    Test() 
    { 
     assert(offsetofclass(B, D) == 0); 

     // you'll need to befriend Test with B & D to make this work 
     // or make the filed public... or just move these asserts 
     // to D's constructor 
     assert(offsetof(B, m_isDerived) == offsetof(D, m_isDerived)); 
    } 
}; 
Test g_test; 

이는 시작에서 실행 얻을 것이다 : 당신은 확실히 오프셋이 동일하게 몇 가지 검사를 배치 할 수 있습니다. 정적 인 어설 션 (실행 및 컴파일 시간)으로 바뀔 수 있다고는 생각하지 않습니다.

+0

파생 된 파생물에 더 많은 멤버가 포함되어 있다는 것을 지적하십시오 (파생되는 것을 알기 전에 호출되지 않습니다). m_isDerived의 오프셋을 변경할 수 있습니까? – DaedalusAlpha

+0

'D'가 가상이고 'B'가 아니거나 'D'가 'B'에서 파생되기 전에 다른 유형에서 유래하지 않으면 그대로 유지됩니다. 내 대답 편집. – gwiazdorrr

+0

모든 답변을 주셔서 감사합니다, 나는 그들 모두가 장점이 있다고 생각하지만, 이것을 골라야하므로 이것을 골라야합니다. – DaedalusAlpha

1

가 주어 당신만큼 단지 B 일들이 작동하지해야하는 이유를 기술적 이유가 없다 액세스

class D : public B 

B* b = new B(); // Create new base class 
D* unknown = static_cast<D*>(b); 

공식적으로이 정의되지 않은 동작이지만,

. 일반적으로 std::stack<T>::c과 같이 액세스 할 수없는 곳에 액세스하려면 B 일을 처리해야합니다. 그러나 멤버 함수 포인터 트릭을 포함하여 더 좋은 방법이 있습니다. 매우 부서지기 쉬운 및 안전하지 않은

B(bool isDerived = false) : m_isDerived(isDerived) {} 

에 관한


.

대신 B 클래스에는 가상 멤버가 있어야합니다. 그런 다음 dynamic_casttypeid을 사용할 수 있습니다. 일반적으로 RTTI, 런타임 유형 정보으로 알려져 있습니다.

그러나 세 번째 손잡이에서 관련 기능을 다운 케스팅이 필요하지 않도록 클래스 B을 통해 사용할 수 있어야합니다.

1

당신이 지적했듯이 하나의 불투명 한 포인터에 임의의 계층 구조를 저장하는 유일한 올바른 접근법은 기본 클래스를 사용하는 것입니다. 이제 독점적으로 설치해야 Base*과 어떤 원시 포인터 타입 사이의 변환합니다

struct Base { virtual ~Base(){}; /* ... */ }; 

struct FooDerived : Base  { /* ... */ }; 
struct BarDerived : Base  { /* ... */ }; 
struct ZipDerived : Base  { /* ... */ }; 

: 그래서, 먼저 계층 구조를 확인하십시오. 엄밀히 말하면 포인터는 또는 uintptr_t에만 저장할 수 있지만 DWORD_PTR은 충분히 넓다고 가정 해 봅시다.

Base * unmarshal_pointer(DWORD_PTR src) 
{ 
    static_assert(sizeof(DWORD_PTR) == sizeof(void *), "Not implementable"); 
    return reinterpret_cast<Base *>(src); 
} 

이 모든 실제 다형성 (polymorphic)이 구현되어야한다 : 반환 방향은 그냥 쉽게

void marshal_pointer(Base const * p, DWORD_PTR & dst) 
{ 
    static_assert(sizeof(DWORD_PTR) == sizeof(void *), "Not implementable"); 
    dst = reinterpret_cast<DWORD_PTR>(p); 
} 

:

우리가 함수에 모든 것을 감싸는 경우

는 upcasting 이미의 돌보아 가능한 경우 가상 기능의 관점에서 (그들은 적절한있어 가끔 있지만) 수동 dynamic_cast의 당신의 최후의 수단이어야한다 :

Base * p = unmarshal_pointer(weird_native_windows_thing); 
p->TakeVirtualAction(); 
관련 문제