2013-04-10 20 views
16

spojrzeć na następujące C++ kodC++ wirtualny układ tabeli MI (wielokrotne dziedziczenie)

class Base1 { 
public: 
    Base1(); 
    virtual ~Base1(); 
    virtual void speakClearly(); 
    virtual Base1 *clone() const; 
protected: 
    float data_Base1; 
}; 

class Base2 { 
public: 
    Base2(); 
    virtual ~Base2(); 
    virtual void mumble(); 
    virtual Base2 *clone() const; 
protected: 
    float data_Base2; 
}; 

class Derived : public Base1, public Base2 { 
public: 
    Derived(); 
    virtual ~Derived(); 
    virtual Derived *clone() const; 
protected: 
    float data_Derived; 
}; 

"wnętrze C++ Object Model" 4,2 mówi, że wirtualny układ tabeli klasy Bazowa1, Base2 i pochodny jest jak to: enter image description here

enter image description here

Moje pytanie brzmi:

wirtualna tabeli SubObject Base1 klasy Derived zawiera Base2::mumble. Dlaczego? Wiem, że wiem, że klasa Derived udostępniła ten wirtualny stół z Base1, więc myślę, że funkcja Base2 nie powinna się tutaj pojawiać. Czy ktoś mógłby mi powiedzieć, dlaczego? Dzięki.

+0

Nie zaszkodzi dodać dodatkowe wpisy do tabeli "Pochodna" po tabelach 'Base1'. Można to zrobić dla zwiększenia wydajności. Biorąc pod uwagę wskaźnik 'Derived *', tańsze jest wywoływanie funkcji wirtualnych za pomocą vtable 'Base1' /' Derived' niż za pomocą vtable 'Base2'. –

+0

Uwaga: sposób prezentowania rzeczy wydaje się być zawalony, w Itanium ABI człon '_vptr' jest w rzeczywistości ** pierwszy **; a także 'Base1' jest ** pierwszym ** elementem' Derived'. –

+1

@ MatthieuM. We wszystkich kompilatorach, jakie widziałem, element '_vptr' (pseudo-) był pierwszą rzeczą w klasie, ale standard oczywiście pozwala na to w dowolnym miejscu. –

Odpowiedz

5

Po pierwsze, wszystkim przypomnę, że projekt rozwiązania polegający na wdrożeniu polimorfizmu jest decyzją ABI poza standardem. Na przykład MSVC i Itanium ABI (a następnie gcc, clang, icc, ...) mają różne sposoby realizacji tego.

Wychodząc z tego, myślę, że jest to optymalizacja pod kątem wyszukiwania.

Ilekroć masz Derived obiekt (lub jeden z jego potomka) i odnośnika elementu mumble, nie trzeba się rzeczywiście dowiedzieć się Base2 podobiekt ale może bezpośrednio działać z Base1 podobiektu (którego adres pokrywa się z Derived podobiektu, więc nie ma udziału w arytmetyce).

+3

Co więcej, kompilator dodał funkcje 'Derived' za funkcjami' Base1', więc ten sam 'vtable' może być użyty dla obu. I 'mumble' jest również funkcją' Derived' (przez dziedziczenie), a nie tylko 'Base2'. Jest to "optymalizacja", ale jest to optymalizacja oparta na podstawach języka. Nie mogę sobie wyobrazić, żeby jakiś kompilator tego nie robił. –

+0

Czy to naprawdę przydatna optymalizacja? Aby _call_ 'mumble', nadal musisz znaleźć podobiekt' Base2' dla jego 'tego' wskaźnika, więc nie zapisałeś żadnej pracy. Jedyną możliwą poprawą jest to, że często sprawdzasz adres 'bełkotu' bez wywoływania go lub jeśli poprawia on współczynnik trafień w pamięci podręcznej na pierwszym podteście vtable. – Useless

+0

@Useless: Muszę przyznać, że nie jestem pewien, czy to jest tak skuteczne; jednak usuwa on zależność od danych. Teraz możesz obliczyć równolegle adres 'mumble' i adres podobieństwa' Base2'. Dlatego procesor może obliczyć 'mumble', rozpocząć ładowanie kodu z pamięci i * w równoległym * obliczeniu podtypu' Base2'. –

0

W czasie wykonywania, gdy pojawi się:

Base2 b2; 
    Base1* b1_ptr = (Base1*)&b2; 
    b1_ptr->mumble(); // will call Base2::mumble(), this is the reason. 

wtedy Base2 :: Mambo() musi być wywołana! Należy zwrócić uwagę, że mumble() jest TYLKO wirtualną metodą, która została nadpisana w hierarchii. (Nawet, możesz pomyśleć, że clone() jest overriden zbyt, ale który zwraca inny typ wśród klas, to jest inny podpis).

+1

Jestem zdezorientowany, nie byłby to błąd kompilatora, ponieważ Base1 nie ma funkcji o nazwie mumble? – Verdagon

Powiązane problemy