2014-05-19 6 views
2

vtable이 실제로 어떻게 빌드되었는지 알아보기로 결심했습니다. 그래서 디버거를 열었고 이상한 것을 발견했습니다. 노드 ptr에는 몇 개의 vptr이 있습니다. 나는 항상 객체 당 하나의 vptr 만 있다고 생각했다. 아무도 나에게 여기에 무슨 일이 일어 났는지 설명 할 수 있니?가상 테이블 포인터

#include <iostream> 
using namespace std; 

class Base 
{ 
    int base; 
public: 
    virtual void say() 
    { 
     cout << "Hello" << endl; 
    } 
    virtual void no() 
    { 
     cout << "No" << endl; 
    } 
}; 


class Base2 
{ 
public: 
    virtual void lol() 
    { 
     cout << "lol" << endl; 
    } 
}; 

class Derv:public Base,public Base2 
{ 
public: 
    void say() 
    { 
     cout << "yep" << endl; 
    } 

}; 


int main() 
{ 

    Base* ptr = new Derv(); 
    ptr->say(); 
    ptr = new Base(); 
    ptr->say(); 
} 

http://s018.radikal.ru/i504/1405/1e/38832e978dd5.jpg

+0

"노드 ptr에 몇 개의 vptr이 포함되어 있습니다."라는 의미를 이해할 수 없습니다. 당신은 당신이 당신이 설명하기를 바라는 것을 정확히 보여줄 수 있습니까? –

+0

'Base'용 vtable과 'Base2'용 vtable. 컴파일러는 가상 테이블을 구현하는 방법을 선택할 수 있습니다. vtables의 구조가 모든 플랫폼에서 동일 할 것으로 예상 할 수 있다고 생각하지 않습니다. –

+0

[link] (http://s018.radikal.ru/i504/1405/1e/38832e978dd5.jpg) – GamovCoder

답변

4

두 포인터가 필요하다.

가 단계적으로 통해 가자 :

먼저 가상 기능을 가지고 Base을 정의합니다. 다음 따라서 컴파일러는 대략 보이는 가상 테이블을 생성한다 (괄호 안에 주어진 인덱스, 이것은 일례이다 유의 정확한 테이블 레이아웃 컴파일러에 의존 할 것이다)

[0] address of Base::say() 
[1] address of Base::no() 

Base 레이아웃이 해당 테이블을 가리키는 필드 __vptr (또는 이름이 지어지면 이름이 지정됩니다.)이됩니다. Base* 유형의 pBase 포인터가 있고 say을 호출하면 컴파일러는 실제로 (p->__vptr[0])()을 호출합니다.

[0] address of Base2::lol() 

Base2 포인터를 통해 lol에 대한 호출이 지금 (pBase2->__vptr[0])()과 같이 번역됩니다

다음 당신은 누구의 가상 테이블과 같이 표시됩니다 두 번째, 독립적 인 클래스 Base2을 정의합니다.

이제는 BaseBase2에서 상속받은 클래스 Derv을 정의합니다. 이는 특히 및 Base2*이 모두 Derv 유형의 개체를 가리킬 수 있음을 의미합니다. 지금 당신은 둘 다 (pXXX->__vptr[0])()로 변환하기 때문에, 같은 함수를 호출하는 것 하나 __vptr, pBase->say()pBase2->lol()이 있다면.

그러나 무엇 실제로 일어나는 일은 __vptr의 필드, Base 기본 클래스에 대한 다른 하나는 _Base2 기본 클래스에 대한 하나 있다는 것입니다. __vptr 자체로 Base2 하위 객체에의 __vptrBase2* 포인트로 Base 하위 객체에 Base* 점. 이제 Derv 가상 테이블은 예를 들어 이런 다음 Base2 하위 객체 포인트의 __vptr 동안 소자에 [2]

[0] address of Derv::say() 
[1] address of Base::no() 
[2] address of Base2::lol() 

해당 테이블의 시작 부분 Base 하위 객체 점의 __vptr.이제 pBase->say()을 호출하면 (pBase->__vptr[0])()으로 변환되고 하위 객체의 __vptrDerv의 가상 테이블의 시작을 가리키며 결국 Derv::say()을 호출하게됩니다. 당신이 호출하면 반면에, pBase2->lol() 그것은 (pBase2->__vptr[0])()로 번역하지만, 될 Base2 하위 객체 외경 Derv-pBase2 점 때문에, 것, 따라서 Derv의 가상 테이블의 요소 [2]의 주소를 가리키는 해당 __vptr 역 참조 Base2::lol이 저장됩니다. 자 이제 Base2::lol()이 의도 된대로 호출되었습니다.

+0

매우 노골적인 답변 주셔서 감사합니다 :) 총계로 : 그래서, Derv 전혀 자신의 vtable을 가지고 있으며 Derv는 두 개의 vtable을 사용하는 대신 하나의 포인터 두 개를 사용합니까? – GamovCoder

+0

@GamovCoder :'Derv'에는 vtable이 있지만 별도의 vptr이 없습니다. 그것은 각각의 기본 클래스의 vptr (s)를 사용합니다. 'Derv'의'Base'와'Base2'는 서로 분리되어 있으므로 각각 별도의 vptr을가집니다. –

+0

Derv의 테이블은 두 개의 테이블로 구성되어 있습니다. 전체가 아닙니다. 그렇지 않습니까? – GamovCoder

1

당신이베이스에 대한 포인터에 파생에 대한 포인터를 캐스팅 할 때 무슨 생각해 (I는 파생 클래스의 객체 때 기본 클래스 포인터 점을 의미), 그것은을 참조해야합니다 기본 유형과 동일한 레이아웃을 갖는 메모리 블록이 있습니다. 상속이 여러 개있을 때 가상 함수가있는 각 기본 데이터베이스에서 하나의 vptr로 끝납니다. 가상 기능이 개 기본 클래스를 가지고 있기 때문에

+0

나는 그것을 얻었지만 실제로는 casting을 연결할 수없고 Derived 클래스의 객체를 포함하는 노드 ptr에 3 개의 vptr이 들어있는 이유는 무엇입니까? 파생 클래스의 객체를 Base 클래스 객체로 캐스팅 할 수 있지만 vptr의 주소는 변경되지 않아야 함을 알고 있습니다. 나는 파생 된 클래스의 객체를 만들 때 주소가 적절한 주소 (파생 된 클래스의 vtable의 주소와 함께)로 대체되는 포인터 하나만 가지고 있다고 생각했습니다. – GamovCoder

+0

@GamovCoder : 나는 이것이 더 많은 인공물이라고 생각합니다. 디버거가 정보를 표시하는 방법. 디버거에 따르면주의 깊게 보면 'Base'객체의 복사본이 2 개 있는데, 분명히 그렇지 않습니다. –