2010-09-19 22 views

Odpowiedz

80

Aby zrozumieć system rzutowania, należy zanurkować w modelu obiektowym.

Klasyczne przedstawienie prostego modelu hierarchii jest powstrzymywanie: że jeśli B wywodzi A następnie przedmiotem B będzie w rzeczywistości zawierać A podobiekt obok własnych cech.

W tym modelu downcasting jest prostą manipulacją wskaźnikową, przez offset znany w czasie kompilacji, który zależy od układu pamięci B.

To co static_cast zrobić: statyczny obsada dubbingu jest statyczna, ponieważ obliczanie, co jest konieczne dla obsada odbywa się w czasie kompilacji, czy to wyżeł arytmetycznych lub konwersji (*).

Jednak, gdy kopie w dziedziczeniu virtual stają się nieco trudniejsze. Głównym problemem jest to, że z dziedziczeniem wszystkie podklasy współdzielą to samo wystąpienie podobiektu. W tym celu B będzie miał wskaźnik do A, zamiast właściwego A, a obiekt klasy bazowej A zostanie utworzony poza B.

Dlatego w czasie kompilacji niemożliwe jest wydedukowanie niezbędnej arytmetyki wskaźnika: zależy to od typu środowiska wykonawczego obiektu.

Zawsze, gdy istnieje zależność typu czasu pracy, potrzebne są informacje RTTI (Informacje o typie RunTime), a korzystanie z RTTI dla rzutowania jest zadaniem dynamic_cast.

Podsumowując:

  • kompilacji przygnębiony: static_cast
  • run-time przygnębiony: dynamic_cast

Pozostałe dwa są również w czasie kompilacji rzuca, ale są tak specyficzne, że łatwo zapamiętać, do czego służą ... i są śmierdzące, więc lepiej nie używać ich w ogóle.

(*) Jak zauważono przez @curiousguy w komentarzach, dotyczy to tylko downcastingu. A static_cast pozwala na upcasting niezależnie od wirtualnego lub prostego dziedziczenia, chociaż wtedy obsada jest również niepotrzebna.

+4

Dobra odpowiedź, która pozwoliła mi zrozumieć, jak naprawdę działa wirtualne dziedzictwo!+1 – undu

+0

Podoba mi się twoja odpowiedź, ale OP najwyraźniej pytał o błąd dla DOWNCASTING zamiast upcasting. – h9uest

+0

@ h9uest: Dzięki, że pokazałam tę wpadkę, zmieniłam "casting" na "downcasting" i wszystko jest już dobrze. –

11

Z tego co wiem, trzeba użyć dynamic_cast, ponieważ dziedziczenie to virtual, a ty jesteś downcasting.

6

W tej sytuacji nie można użyć static_cast, ponieważ kompilator nie zna przesunięcia B względem A w czasie kompilacji. Przesunięcie musi być obliczane w czasie wykonywania w oparciu o dokładny typ najbardziej wyprowadzonego obiektu. Dlatego musisz użyć dynamic_cast.

+0

Podczas konwersji wyprowadzonej na wirtualną bazę można użyć 'static_cast'. – curiousguy

+0

@całkowite: tak, ale pytanie dotyczy konwersji bazy na pochodną. – ybungalobill

+0

Nie jest jasne, dlaczego argument nie ma zastosowania również do pochodnych. – curiousguy

4

Tak, musisz użyć dynamic_cast, ale będziesz musiał dokonać polimorficznej klasy podstawowej A, np. przez dodanie wirtualnego dtor.

+1

lub dodanie co najmniej jednej metody wirtualnej. – Liton

4

Według standardowych docs,

Sekcja 5.2.9 - 9 dla Static Obsada,

An rvalue of type “pointer to cv1 B,” where B is a class type, can be converted to an rvalue of type “pointer to cv2 D,” where D is a class derived (clause 10) from B, if a valid standard conversion from “pointer to D” to “pointer to B” exists (4.10), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is neither a virtual base class of D nor a base class of a virtual base class of D.

Dlatego też nie jest możliwe, należy użyć dynamic_cast ...

1

$5.2.9/2- "An expression e can be explicitly converted to a type T using a static_cast of the form static_cast(e) if the declaration “T t(e);” is well-formed, for some invented temporary variable t (8.5)."

W kodzie próbujesz static_cast 'T = B *' i 'e = A *'

Teraz 'B * t (A *)' nie jest dobrze uformowane w C++ (ale "A * t (B *)" jest, ponieważ "A" jest wirtualną jednoznaczną i dostępną podstawą "B", dlatego kod daje błąd:

+0

Błędna wycena. – curiousguy

1

Nie wiem, czy to jest "bezpieczne", ale

Założenie:

B pochodzi od A (a Czysty wirtualny)

ponieważ wiem, że wskaźnik do B nadal wskaźnik do B.

class A 
    { 
      virtual void doSomething(const void* p) const =0; 
    }; 

    class B 
    { 
    public: 
      int value; 
      virtual void doSomething(const void*p)const 
      { 
      const B * other = reinterpret_cast<const B*>(p); 
      cout<<"hello!"<< other->value <<endl; 
      } 
    }; 

    int main() 
    { 
      B foo(1),bar(2); 
      A * p = &foo, q=&bar; 
      p->doSomething(q); 
      return 0; 
    } 

ten program wykonuje prawidłowo i powrócić drukowania "Hello!" oraz wartość innego obiektu (w tym przypadku "2").

Przy okazji, to co robię jest bardzo niebezpieczne (osobiście daję inny identyfikator każdej klasie i twierdzę, że po ponownym zinterpretowaniu castingu obecny identyfikator jest równy innemu ID, aby mieć pewność, że robimy coś z 2 równymi klasy) i jak widzisz ograniczyłem się do metod "const". W ten sposób będzie działać z metodami "niestałymi", ale jeśli zrobisz coś złego, złapanie błędu będzie prawie niemożliwe. I nawet przy twierdzeniu istnieje 1 szansa z 4 miliardów na twierdzenie, nawet jeśli ma się nie powieść (assert (ID == inny-> ID);)

Przy okazji ... Dobry projekt OO powinien nie wymaga tego rodzaju rzeczy, ale w moim przypadku próbowałem refaktoryzować/przeprojektować kod, nie mogąc zrezygnować z używania odlewania reinterpretacyjnego. ogólnie mówiąc, MOŻESZ unikać tego rodzaju rzeczy.

+0

Czy jesteś pewien, że to konieczne? –

+0

jest to specyficzne dla twojego problemu. Przeprojektowanie powinno w większości przypadków temu zapobiec (unikaj mojego przykładu, jeśli potrafisz). oh kochanie, zapominam o "const". – GameDeveloper

+0

Mam na myśli czy jesteś tego pewien? Co się stanie, jeśli podam doSomething int wskaźnik? To by się nie udało katastrofalnie. Dlaczego nie skorzystać z dynamicznej obsady i sprawdzić wynik? Nie wiem, jakie są twoje dokładne wymagania, ale wierzę, że jeśli wprowadzisz statyczny system polimorfizmu (np. Wzór CRTP), możesz wymyślić coś bezpieczniejszego. –