2009-11-17 4 views
3

다중 상속을 사용하는 경우 C++은 공통 기본 클래스의 "여러보기"를 갖는 여러 vtable을 유지 관리해야합니다. 그것은 다음과 같은 출력을 생성다중 상속에서 오브젝트 포인터가 다른 경우에도 왜 동일합니까?

#include "stdafx.h" 
#include <Windows.h> 

void dumpPointer(void* pointer) 
{ 
    __int64 thisPointer = reinterpret_cast<__int64>(pointer); 
    char buffer[100]; 
    _i64toa(thisPointer, buffer, 10); 
    OutputDebugStringA(buffer); 
    OutputDebugStringA("\n"); 
} 

class ICommonBase { 
public: 
    virtual void Common() = 0 {} 
}; 

class IDerived1 : public ICommonBase { 
}; 

class IDerived2 : public ICommonBase { 
}; 

class CClass : public IDerived1, public IDerived2 { 
public: 
    virtual void Common() { 
     dumpPointer(this); 
    } 
    int stuff; 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    CClass* object = new CClass(); 
    object->Common(); 
    ICommonBase* casted1 = static_cast<ICommonBase*>(static_cast<IDerived1*>(object)); 
    casted1->Common(); 
    dumpPointer(casted1); 

    ICommonBase* casted2 = static_cast<ICommonBase*>(static_cast<IDerived2*>(object)); 
    casted2->Common(); 
    dumpPointer(casted2); 

    return 0; 
} 

:

206968 //CClass::Common this 
206968 //(ICommonBase)IDerived1::Common this 
206968 //(ICommonBase)IDerived1* casted1 
206968 //(ICommonBase)IDerived2::Common this 
206972 //(ICommonBase)IDerived2* casted2 

여기 casted1casted2가 서로 다른 하위 객체를 가리 이후 합리적인 다른 값을 가질

는 여기에 코드입니다. 가상 함수가 호출 될 때 기본 클래스에 대한 캐스트가 완료되고 컴파일러는 원래 파생 클래스 였는지 여부를 알지 못합니다. 여전히 은 매번 동일합니다. 어떻게됩니까?

+2

왜 중요한가요? 이는 C++ 코드와 관련이없는 구현 세부 사항입니다. 컴파일러를 작성하는 경우에만이 세부 사항을 알아야합니다. 사실 나는 세부 사항이 컴파일러간에 동일하게 유지 될 것이라고 확신하지는 않습니다. –

+0

@ Martin 당신의 감정에 동의하지만, 컴파일러가 직면 한 문제는주의를 기울여야 할 흥미로운 것입니다. 예를 들어 알고 있어야하는 경우 가끔은 실제 결과가있는 경우도 있습니다. 캐스트가 특정 방식으로 작동합니다. – asveikau

+0

가상 상속을 사용하지 않고 다이아몬드 상속을 사용 했으므로 그렇게해서는 안됩니다. 아마도 CClass의 vtable에 'Common'이 두 번있을 수 있으므로이 테스트가 어떻게 영향을 미치는지는 알 수 없습니다. –

답변

3

매우 영리한, 가상 함수의 호출은 종종 그는 this 포인터를 조정하는 '썽크'로 이동합니다. CClassIDerived1 하위 객체 인 CClass 객체의 시작 (일치하는 일 becuase casted1 포인터 값이 CClassobject 포인터와 동일한 이유를 예에서 casted1 포인터의 VTBL 항목은 썽크를 필요로하지 않습니다). VTBL 기능 포인터가 실제로 썽크 대신 직접에 CClass::Common() 함수를 가리 키도록

그러나, IDerived2 하위 개체에 casted2 포인터는 CClass 객체의 시작과 일치하지 않습니다. 썽크는 개체를 가리 키도록 this 포인터를 조정 한 다음 CClass::Common() 함수로 점프합니다. 따라서 호출 된 하위 개체 포인터의 유형에 관계없이 CClass 개체의 시작 부분에 대한 포인터를 항상 가져옵니다.

S tanley Lippman's "Inside the C++ Object Model" book, 4.2 "가상 멤버 함수/가상 함수 아래의 MI"섹션에 이에 대한 설명이 있습니다.

+0

제쳐두고 - 이것은 MI가있는 가상 함수의 구현 중 하나이지만 일반적이며 MSVC에서 사용되는 것입니다. 그러나이 특정 구현이 표준에 의해 지정되지 않은 경우에도 이러한 유형의 작업이 어떻게 작동 하는지를 아는 것은 여전히 ​​유용합니다. –

+0

@Michael, 가능한 구현과 충돌하지 않을 정도로 일반적인 설명을 이해합니다. 하위 객체에 대한 포인터는 일치하지 않으며 가상 메소드를 호출하기 전에 시스템은 메소드에 전달 될 때 해당 메소드가 구현 된 (하위) 객체의 시작을 참조하도록'this' 포인터를 채택해야합니다. 이것이 어떻게 이루어지며 '썽크'처럼 보이는 것은 구현 정의입니다. (또한 나는 생각에 대해 upvoted vtable 썽크 가리키고 생각하지 않지만 정보 대신 호출자의 위치에서 컴파일러에서 포인터를 조정할 수 있습니다. –

+0

'this'는 전통적으로 전달됩니다. 첫 번째 (숨겨진) 매개 변수는 객체에 바인딩 된 메서드 호출에 전달됩니다.그래서 그것은 vtable의 메소드가 'thunk'를 전달하는 것과 비슷합니다.> 같은 클래스의 모든 인스턴스에 대해 하나의 vtable 만 있으면 다중 스레드 환경에서 실행됩니다. –

1

이 메모리에서 개체의 레이아웃을 볼 경우는 다음과 같이 될 것입니다 :

귀하의 this 것 .. 그냥 아이디어를 제공하는 otherway also..but 수 있습니다

v-pointer for IDerived1 
v-pointer for IDerived2 
.... 
.... 

항상 IDerived1에 대한 v 포인터가 저장되는 오브젝트의 시작점을 가리 킵니다. 그러나 포인터를 IDetived2으로 캐스팅하면 캐스팅 된 포인터는 IDerived2의 v- 포인터를 가리키고 sizeof (포인터)는 this 포인터에서 오프셋됩니다.

3

다른 유형으로 캐스트 될 때 vtable의 항목뿐만 아니라 필드의 오프셋도 일관된 위치에 있어야합니다. ICommonBase*을 매개 변수로 사용하는 코드는 실제로 해당 개체가 IDerived2임을 알지 못합니다. 그러나 여전히 ->foo을 참조 해제하거나 가상 메소드 bar()을 호출 할 수 있어야합니다. 이것들이 작동 할 방법이없는 예측 가능한 주소에 있지 않다면.

단일 상속의 경우이 작업을 쉽게 수행 할 수 있습니다. DerivedBase에서 상속 된 경우 Derived의 오프셋 0도 Base의 오프셋 0이며 Derived의 고유 한 멤버는 Base의 마지막 멤버 뒤에 올 수 있다고 말할 수 있습니다. Base1의 첫 번째 바이트가 Base2의 첫 번째 바이트가 될 수 없기 때문에 분명히 작동하지 않는 다중 상속의 경우. 각자는 자신 만의 공간이 필요합니다. 두 (Foo를 호출)에서 상속 이러한 클래스가 있다면

그래서, 컴파일러가 타입 Foo에 대한 것을 알 수 X 오프셋에서의 Base1 부분은 시작하고 주조 할 때 Base2 부분은 오프셋 Y.에서 시작 어느 쪽의 타입으로도, 컴파일러는 적절한 오프셋을 this에 추가 할 수 있습니다. 그것의 단지 특정 인스턴스를 모든 구성원에 액세스 할 수 있도록

구현이 Foo에 의해 제공됩니다 Foo의 실제 가상 메서드를 호출

, 그것은 여전히 ​​개체에 "진짜"포인터를 필요 베이스 Base1 또는 Base2. 따라서 this은 여전히 ​​"실제"객체를 가리킬 필요가 있습니다.

구현 세부 사항은 설명 된 것과 다를 수 있습니다. 이는 문제가 발생한 이유에 대한 상위 수준의 설명입니다.

1

G ++ 4.3에는 동일한 객체 모델이 있습니다 (Naveen의 답변 참조). casted1과 casted2는 다른 값을가집니다.

ICommonBase* casted1 = (ICommonBase*)(IDerived1*)object; 
ICommonBase* casted2 = (ICommonBase*)(IDerived2*)object; 

결과는 동일합니다

는 또한, G ++ 4.3에서, 심지어는 잔인한 캐스팅을 사용합니다. 다중 상속은 가상 함수 호출에 사용되는 컴파일러

+0

"잔인한"캐스팅이 아니라 static_cast입니다. 실패한 것을보고 싶다면'reinterpret_cast'를 시도하십시오. – MSalters

관련 문제