2009-02-24 13 views
8

Ostatnio pracowałem nad fragmentem kodu C++ dla projektu pobocznego (cpp-markdown library, dla ciekawskich), i wpadłem na pytanie o kodowanie, na które chciałbym się wypowiedzieć.Unikanie dynamic_cast/RTTI

cpp-markdown ma klasę podstawową o nazwie Token, która ma pewną liczbę podklas. Dwie główne podklasy to Container (która przechowuje zbiory innych Token s) i TextHolder (używane jako klasa podstawowa dla Token s, które zawierają tekst, oczywiście).

Większość przetwarzania jest obsługiwana za pomocą funkcji wirtualnych, ale niektóre z nich były lepiej obsługiwane w ramach jednej funkcji. W tym celu użyłem dynamic_cast, aby obniżyć wskaźnik z Token* do jednej z jego podklas, więc mogłem wywoływać funkcje, które są specyficzne dla podklasy i jej klas potomnych. Nie ma szans, aby rzutowanie zakończyło się niepowodzeniem, ponieważ kod jest w stanie stwierdzić, kiedy takie rzeczy są potrzebne za pośrednictwem funkcji wirtualnych (takich jak isUnmatchedOpenMarker).

Istnieją dwa inne sposoby Widziałam obsłużyć to:

  1. Tworzenie wszystko funkcji, które chcę zadzwonić jako wirtualne funkcje Token i po prostu zostawić je z pustym ciała dla każda podklasa wyjątkiem jednego (ów), które muszą je obsługiwać, albo ...

  2. Tworzenie funkcji wirtualnego w Token że zwrotu prawidłowo wpisany wskaźnik this kiedy to się nazywa w niektórych podtypów i null pointer jeśli został wywołany na cokolwiek innego. Zasadniczo rozszerzenie wirtualnego systemu funkcji, którego już tam używam.

Druga metoda wydaje mi się lepsza niż dotychczasowa i pierwsza. Ale chciałbym poznać innych doświadczonych twórców C++. A może martwię się zbytnio o bzdury. :-)

Odpowiedz

20

# 1 zanieczyszcza przestrzeń nazw klasy i vtable dla obiektów, które jej nie potrzebują. Ok, gdy masz garstkę metod, które będą na ogół zaimplementowane, ale proste, gdy są potrzebne tylko dla jednej klasy pochodnej.

# 2 to po prostu dynamic_cast<> w sukni w groszki i szminki. Nie czyni kodu klienta prostszym i splątuje całą hierarchię, wymagając, aby klasa podstawowa i każda klasa pochodna była częściowo świadoma co drugiej klasy pochodnej.

Po prostu użyj dynamic_cast<>. Po to tam jest.

4

Jeśli chcesz uzyskać sprytny, możesz również zbudować wzór double dispatch, który stanowi dwie trzecie wartości visitor pattern.

  • Utwórz podstawową klasę TokenVisitor zawierającą puste wirtualne metody visit(SpecificToken*).
  • Dodaj pojedynczą wirtualną metodę accept(TokenVisitor*) do tokena, który wywołuje poprawnie wpisaną metodę na podanym TokenVisitor.
  • Wyprowadzić z TokenVisitor dla różnych rzeczy, które należy wykonać w różny sposób na wszystkich tokenach.

Pełen odwiedzający, przydatne do konstrukcji drzew, mają domyślnie accept metody iterację nad dziećmi zawijających token->accept(this); na siebie.

4

Jeśli wiesz, że konwersja nie może być nieważna, po prostu użyj static_cast.

+1

Uzgodnione, jeśli już sprawdzasz typ za pomocą funkcji wirtualnej, wtedy dynamic_cast jest bezużyteczne. Oba wasze inne rozwiązania to po prostu białe pranie, że wasza hierarchia jest zepsuta. Jeśli chcesz się włamać, użyj przynajmniej szybszego hacka. – BigSandwich

+1

static_cast nie działa, jeśli klasa pochodna używa dziedziczenia wirtualnego do dziedziczenia z tokena. dynamic_cast nie byłby w tym przypadku bezużyteczny. – bk1e

2

Dlaczego nie chcesz używać dynamic_cast? Czy powoduje to niedopuszczalne wąskie gardło w twojej aplikacji? Jeśli nie, może nie warto teraz nic robić z kodem.

Jeśli dobrze sobie radzisz z bezpieczeństwem w zamian za odrobinę prędkości w swojej konkretnej sytuacji, powinieneś być w porządku, robiąc static_cast; jednak to potwierdza twoje przypuszczenie, że znasz typ obiektu i nie ma szans, że obsada będzie zła. Jeśli twoje założenie się nie powiedzie później, możesz skończyć z tajemniczymi błędami błędów w twoim kodzie. Wracając do mojego pierwotnego pytania, czy jesteś naprawdę pewien, że ten interes jest warty tutaj?

Jeśli chodzi o wymienione opcje: Pierwszy z nich nie brzmi tak, jak rozwiązanie w ostatniej chwili, jak się spodziewałem, gdy ktoś napisał kod o 3 nad ranem. Funkcjonalność docierająca do podstawy hierarchii klasowej jest jednym z najczęstszych wzorców anty-wzorowanych na ludziach nowych w OOP. Nie rób tego.

Jeśli chodzi o drugą opcję, na której wymieniono jakąkolwiek opcję, to po prostu reimplementacja dynamic_cast - jeśli jesteś na platformie z dostępnymi tylko kompilatorami (słyszałem opowieści o kompilatorze Gamecube zajmującym jedną czwartą dostępnej pamięci RAM systemu z informacją RTTI) może się to opłacać, ale bardziej niż prawdopodobne jest, że marnujesz swój czas. Czy jesteś naprawdę pewien, że jest to coś, o czym warto się martwić?

+0

Nie, nie jestem pewien, czy jest to warte uwagi. Dlatego spytałem. :-) –

+0

Jak wspomniano powyżej: static_cast nie powiedzie się, jeśli korzystasz z wirtualnego dziedziczenia. – mmmmmmmm

1

Prawdziwym sposobem uniknięcia dynamic_cast jest posiadanie odpowiednich wskaźników we właściwym miejscu. Abstrakcja powinna zająć się resztą.

IMHO, powodem, dla którego dynamic_cast ma tę reputację, jest to, że jej wydajność pogarsza się trochę za każdym razem, gdy dodajesz inny podtyp w hierarchii klas. Jeśli masz 4-5 klas w hierarchii, nie ma się czym martwić.

1

Zabawne rzeczy. dynamic_cast w tokenierzu oznacza, że ​​chcesz faktycznie podzielić Token na coś, co tworzy token w oparciu o bieżącą pozycję w strumieniu tekstu i logikę w tokenie. Każdy token będzie albo samowystarczalny, albo będzie musiał utworzyć Token, tak jak powyżej, aby poprawnie przetwarzać tekst.

W ten sposób można wyciągnąć ogólne elementy i nadal można je analizować w oparciu o hierarchię tokenów. Możesz nawet sterować tym całym parserem danymi bez używania dynamic_cast.