2012-06-25 46 views
11

Czy interfejsy (klasa polimorficzna wyłącznie z czystymi funkcjami wirtualnymi) mają vtable? Ponieważ interfejsy same nie implementują funkcji polimorficznej i nie można ich bezpośrednio zbudować, nie byłoby potrzeby, aby linker umieścił tabelę vtable. Czy tak jest? Jestem szczególnie zaniepokojony kompilatorem MSVC.Interfejs vtable

+0

Bardzo ważną informacją jest funkcja 'declspec (novtable)' w MSVC: pozwala interfejsom, zwłaszcza COM, na pominięcie tabeli vtable. Ma to pewne interesujące i znaczące implikacje, jeśli chodzi o dziedziczenie (wymusza pojedyncze dziedziczenie, ale powoduje, że ostateczny obiekt ma tylko jedną tabelę zapewniającą więcej polimorfizmów niż w innych przypadkach). – ssube

Odpowiedz

6

Tak robią. Istnieje wiele dobrych powodów.

Pierwszym dobrym powodem jest to, że nawet czyste wirtualne metody mają implementację. Niejawne lub jawne. Stosunkowo łatwo jest ściągnąć sztuczkę wywołującą czystą funkcję wirtualną, więc możesz zasadniczo podać definicję jednej z twoich, zadzwoń i zobacz, co się stanie. Z tego powodu powinien powstać wirtualny stół.

Jest jeszcze jeden powód umieszczenia wirtualnej tabeli w klasie bazowej, nawet jeśli wszystkie jej metody są czysto wirtualne i nie ma żadnych innych elementów danych. Kiedy używany jest polimorfizm, wskaźnik do klasy bazowej jest przekazywany dookoła programu. Aby wywołać metodę wirtualną, kompilator/środowisko wykonawcze powinno ustalić względne przesunięcie tabeli wirtualnej od wskaźnika bazowego. Jeśli C++ nie ma dziedziczenia wielokrotnego, można założyć zerowe przesunięcie od abstrakcyjnej klasy bazowej (na przykład), w którym to przypadku byłoby możliwe, że nie będzie tam vtable (ale nadal potrzebujemy go z powodu nr 1). Ponieważ jednak istnieje dziedziczenie wielokrotne, sztuczka ala "vtable jest przy 0 offset" nie zadziała, ponieważ mogą istnieć dwa lub trzy vtables w zależności od liczby (i typu) klas bazowych.

Mogły być inne powody, których nie miałem tak dobrze.

Mam nadzieję, że to pomaga.

+0

Dobra argumentacja! Nie pomyślałem o tym. –

+2

Podstęp, do którego odnosisz się, czy to nie wymagało zdefiniowania czystej funkcji wirtualnej (pod _odr_ regułami), czy w inny sposób powodowało niezdefiniowane zachowanie, ponieważ nie wiedziałem o takiej sztuczce? –

+3

Nie jestem przekonany do twojego pierwszego powodu. Jestem całkiem pewny, że jedynymi sposobami wywołania czystej funkcji wirtualnej są (a) wywołanie jej nie-wirtualnie, która nie używa vtable, lub (b) wywołanie niezdefiniowanego zachowania przez wywołanie go z konstruktora/destruktora częściowego skonstruowany obiekt, który nie jest wymagany do wywoływania go w ogóle. Czy znasz inną sztuczkę, której nie mam? –

5

Z czysto C++ punktu widzenia to akademickie pytanie. Funkcje wirtualne nie muszą być implementowane za pomocą vtables, jeśli nie ma na nie przenośnego sposobu.

Jeśli niepokoi Cię szczególnie kompilator MSVC, możesz udekorować swoje interfejsy za pomocą __declspec(novtable).

(W ogóle, w typowych implementacjach, klasa abstrakcyjna może potrzebować vtable, np .:

struct Base { 
    Base(); 
    virtual void f() {} 
    virtual void g() = 0; 
}; 

void h(Base& b) { 
    b.f(); // Call f on a Base that is not (yet) a Derived 
      // vtable for Base required 
} 

Base::Base() { 
    h(*this); 
} 

struct Derived : Base { 
    void g() {} 
}; 

int main() { 
    Derived d; 
} 

)

+0

Chcę zobaczyć, w jaki sposób można je zaimplementować, nie używając wirtualnych tabel ani innych metod, w których co najmniej sizeof (void *) muszą być dodane do klasy służącej jako "punkt wyjścia" ... –

+1

@ VladLazarenko: Myślę, że to prawie powszechnie przyjmuje się, że rozwiązanie oparte na vptr/vtable jest optymalne, ale praktyczne zastosowania, takie jak przechowywanie mapy adresu do informacji o typie dynamicznym w jakiejś formie, są co najmniej teoretycznie możliwe. –

+0

Masz rację.Myślę, że mam na myśli to, że bez względu na to, jak to nazwiesz, będziesz potrzebował przynajmniej punktu wyjścia, aby uzyskać do niego dostęp: wskaźnik do vtable, wskaźnik do hasza z adresami lub skrót do jakiegoś globalnego skrótu z mapami do funkcji wskaźniki. –

1

vtable nie jest konieczne, ale rzadko zoptymalizowane. MSVC udostępnia rozszerzenie __declspec(novtable), które wyraźnie informuje kompilator, że vtable można usunąć. W przypadku braku tego, kompilator musiałby sam sprawdzić, czy vtable nie jest używany. Nie jest to wyjątkowo trudne, ale wciąż dalekie od trywialności. A ponieważ nie zapewnia rzeczywistych korzyści prędkości w zwykłym kodzie, kontrola nie jest zaimplementowana w żadnym znanym kompilatorze.