2012-07-02 6 views
15

To pytanie jest zainspirowane komentarzami here.Czy zgodny z normą kod odrzucający kompilator zawierający downcast dynamic_cast od typu nie-polimorficznego?

Rozważmy następujący fragment kodu:

struct X {}; // no virtual members 
struct Y : X {}; // may or may not have virtual members, doesn't matter 

Y* func(X* x) { return dynamic_cast<Y*>(x); } 

Kilka osób sugeruje, że ich kompilator byłoby odrzucić ciało func.

Jednak wydaje mi się, że to, czy jest to określone przez Standard, zależy od wartości wykonawczej x. Z sekcji 5.2.7 ([expr.dynamic.cast])

  1. Wynikiem ekspresji dynamic_cast<T>(v) jest wynikiem konwersji ekspresję v wpisać T. T będzie wskaźnikiem lub odniesieniem do pełnego typu klasy lub "wskaźnikiem do cvvoid." Operator nie powinien odrzucać stałej.

  2. Jeśli T jest rodzajem wskaźnika, v powinny być prvalue od wskaźnika ukończenia klasy rodzaj, a wynik jest prvalue typu T. Jeśli T jest typem odniesienia o wartości l , v będzie lwartością pełnego typu klasy, a wynik jest lwartością typu określonego przez T. Jeśli T jest typem odniesienia rvalue , to wyrażenie v będzie wyrażeniem mającym pełny typ klasy, , a wynikiem jest wartość x typu określonego przez T.

  3. Jeśli typ v jest taka sama jak T, czy to jest taki sam jak T wyjątkiem tego, że klasa typu obiekt w T jest bardziej cv wykwalifikowany niż typ klasy obiektu w v, wynik jest v (w razie potrzeby przekonwertowane).

  4. Jeżeli wartość v jest wartość null pointer w przypadku wskaźnika, wynik jest wartość null pointer typu T.

  5. Jeśli T jest "wskaźnik do CV1B" i v ma typ „wskaźnik do CV2D" tak, że B to klasa bazowa D, wynik jest wskaźnik unikalnemu B podobiekt obiektu D wskazany przez v. Podobnie, T „definicja CV1B” i v ma typ CV2D tak, że B jest klasa podstawy D wynik jest unikalny B podobiekt obiektu D określoną przez v. Rezultatem jest wartość l, jeśli T jest wartością odniesienia o wartości l lub wartością x, jeśli T jest referencją rVue. W obu wskaźnik i odniesienia przypadkach program jest źle sformułowane jeśli CV2 ma większą CV kwalifikacje niż CV1 czy B jest niedostępne lub niejednoznaczne klasa bazowa D.

  6. W przeciwnym razie, v będzie wskaźnikiem lub lwartością typu polimorficznego.

  7. Jeśli T jest „wskaźnik do cvvoid”, wtedy wynik jest wskaźnikiem do najbardziej pochodzącego obiektu wskazywanego przez v. W przeciwnym razie sprawdzanie czasu wykonania zostanie zastosowane, aby zobaczyć , jeśli obiekt wskazany lub określony przez v może zostać przekonwertowany na typ wskazany lub określony przez T. Najbardziej wyprowadzony obiekt wskazany jako lub określony przez v może zawierać inne B obiekty jako klasy podstawowe, ale są one ignorowane.

  8. Jeśli C jest typem klasy, do której T punktów lub odsyła sprawdzenie czasu wykonywania logicznie wykonywany jako następująco:

    • Jeżeli w najbardziej obiektu pochodzącego wskazał (o którym mowa) do o v, v punktów (obliczono) do klasy public bazowej podobiektu z C obiektu, a jeżeli tylko jeden przedmiot typu C pochodzi od podobiektu wskazał (o którym mowa), aby przez v the punktów wyniku (dotyczy) do tego obiektu C.

    • przeciwnym razie, jeśli v punktów (obliczono) do klasy public bazowej podobiektu przedmiotu najbardziej pochodnej, i rodzaju przedmiotu najbardziej pochodzącego posiada klasę zasady, typu C, jednoznacznej i public , punkty wynikowe (odwołanie) do podobiektu obiektu pochodnego.

    • W przeciwnym razie sprawdzanie w czasie pracy nie powiedzie się.

  9. Wartość nieudanej oddanych do typu wskaźnika jest wartość zerowa wskaźnik wymaganego typu wynikowego. Nieudany rzut do rzutów typu std::bad_cast.

Sposób czytam ten wymóg typu polimorficznych zastosowanie jedynie wtedy, gdy żaden z powyższych warunków nie jest spełniony, a jeden z tych warunków zależy od wartości wykonawczego.

Oczywiście w kilku przypadkach kompilator może pozytywnie określić, że dane wejściowe nie mogą mieć poprawnej wartości NULL (na przykład, gdy jest to wskaźnik this), ale nadal uważam, że kompilator nie może odrzucić kodu, chyba że może ustalić, że oświadczenie zostanie osiągnięte (zwykle jest to pytanie dotyczące czasu pracy).

Diagnostyka ostrzegawcza jest oczywiście tutaj wartościowa, ale czy jest ona zgodna ze standardem, aby kompilator odrzucił ten kod z błędem?

+1

6. jest prawie kryształowo czysty, prawda? Zwłaszcza, że ​​wszystko powyżej nie ma zastosowania. –

+2

@AlexandreC .: Jak można stwierdzić, z wyświetlonego kodu, że (4) nie ma zastosowania? Zwróć uwagę, że jest to "wartość pustego wskaźnika *", która może nie być znana do czasu wykonania, a nie "wskaźnik zerowy * literał *" lub "stała zerowa * stała *". –

+0

(Nie jestem downvoter). Rzeczywiście, czytałem "zerowy wskaźnik" dosłowny "" lub coś podobnego. Czy próbujesz sugerować, że kompilator powinien przetłumaczyć rzutowanie dynamiczne na "assert (! V)" lub coś podobnego w przypadku nie polimorficznym? –

Odpowiedz

3

Bardzo dobry punkt.

Należy zauważyć, że w C++ 03 Brzmienie 5.2.7 i 5.2.7/3/4 jest następująco

Jeśli typ przeciwko jest taka sama jak wymagana typ wynik (który dla wygody, będzie nazwane R w tym opisie), czy jest to ten sam jak R z wyjątkiem, że typ klasy obiektu w R jest bardziej cv wykwalifikowany niż obiektu klasy Wpisz v, wynikiem jest v (w razie potrzeby przekonwertowany).

Jeżeli wartość v jest zerowa wartość wskaźnika w przypadku wskaźnika wynik jest zerowa wartość wskaźnika typu R.

Odniesienie do typu R wprowadzono 5.2.7/3 wydaje się wskazywać, że 5.2.7/4 ma być podpunktu 5.2.7/3. Innymi słowy, wydaje się, że 5.2.7/4 ma zastosowanie tylko w warunkach opisanych w 5.2.7/3, tj. Gdy typy są takie same.

Jednak brzmienie w C++ 11 jest inne i nie obejmuje już R, co nie sugeruje już żadnej specjalnej relacji między 5.2.7/3 a 5.2.7/4. Zastanawiam się, czy zostało to celowo zmienione ...

+0

Biorąc pod uwagę, że wszystkie kompilatory mam dostęp do tego samego błędu w trybie C++ 0x/11, jak w trybie C++ 98/03, a większość z nich dotyczy osób, które były w komisji, podejrzewam, że nie było Celowo się zmienił. – abarnert

+0

Interpretowanie (4) jako podpunktu (3) nie ma sensu. (3) już stwierdził, jaki jest wynik, to jest "v". Dodanie bitu o zerowych wartościach wskaźnika jest w 100% zbędne, jeśli spełnione są wymagania (3). Mimo to jest to bardzo dobra obserwacja dotycząca wyglądu 'R'. –

+0

@ABarnert: Tak, ale teraz sformułowanie użyte w C++ 11 jest mylące z powodów podanych w OP. 5.2.7 jest znane, aby postępować zgodnie ze strukturą "drabiny jeśli warunki", ale to potencjalnie sprawia, że ​​wierzymy, że wskaźniki zerowe powinny być dozwolone bezwarunkowo. – AnT

3

Sądzę, że intencją tego sformułowania jest to, że niektóre odlewy można wykonać podczas kompilacji, np. upcasts lub dynamic_cast<Y*>((X*)0), ale inne wymagają sprawdzenia w czasie działania (w takim przypadku wymagany jest typ polimorficzny).

Jeśli fragment kodu był dobrze uformowany, konieczne byłoby sprawdzenie działania, aby sprawdzić, czy jest on pusty wartość wskaźnika, co jest sprzeczne z ideą, że sprawdzanie w czasie rzeczywistym powinno mieć miejsce tylko w przypadku polimorfizmu.

Zobacz, DR 665, które wyjaśniło, że niektóre odlewy są źle sformułowane podczas kompilacji, a nie przełożone na czas wykonywania.

+0

Nie sądzę, że stała null-wskaźnik może być używany jako argument dla 'dynamic_cast'. 'dynamic_cast' zezwala na wartość null-pointer, ale stała o wartości null-point nie może zostać przekształcona w wartość zerową wskaźnika bez rzutowania. – AnT

+0

Edytowane, aby naprawić przykład i usunąć ostatni akapit, który jestem mniej pewny –

+0

+1 dla odniesienia do DR 665. – abarnert

1

Dla mnie wydaje się to całkiem jasne. Myślę, że zamieszanie pojawia się, gdy dokonujesz błędnej interpretacji, że wyliczanie wymagań jest typem "innego, jeśli ... innego, jeśli ...".

Punkty (1) i (2) po prostu definiują, jakie statyczne typy wejściowe i wyjściowe mogą być dozwolone, pod względem kwalifikacji cv i wartości l-wartości-wartości itd. Tak to jest trywialne i dotyczy wszystkich przypadków .

Punkt (3) jest całkiem jasny, jeśli zarówno typ wejściowy, jak i wyjściowy są takie same (dodane kwalifikatory cv na bok), konwersja jest trywialna (brak lub tylko dodane kwalifikatory cv).

Punkt (4) wyraźnie wskazuje, że jeśli wskaźnik wejściowy ma wartość null, wskaźnik wyjściowy również ma wartość zerową. Punkt ten musi być wprowadzony jako wymóg, a nie jako kwestia odrzucenia lub zaakceptowania obsady (za pomocą analizy statycznej), ale w celu podkreślenia faktu, że jeśli konwersja ze wskaźnika wejściowego na wskaźnik wyjściowy normalnie pociągałaby za sobą przesunięcie do rzeczywistą wartość wskaźnika (tak jak w hierarchii dziedziczenia wielu dziedziczek), wówczas to przesunięcie nie może być zastosowane, jeśli wskaźnik wejściowy ma wartość zerową, aby zachować "zerowość" wskaźnika. Oznacza to po prostu, że podczas wykonywania rzutowania dynamicznego wskaźnik jest sprawdzany pod kątem nieważności, a jeśli jest pusty, wynikowy wskaźnik również musi mieć wartość null.

Punkt (5) po prostu stwierdza, że ​​jeśli jest upcastem (od pochodnej do bazy), to obsada jest statycznie rozpatrywana (odpowiednik static_cast<T>(v)). Chodzi głównie o to, aby obsłużyć przypadek (jak wskazuje przypis), gdzie upcast jest dobrze uformowany, ale może istnieć potencjał dla źle uformowanej obsady, jeśli przejdzie się do najbardziej pochodnego obiektu wskazanego przez v (np. jeśli v faktyczne wskazuje na obiekt pochodny z wieloma klasami bazowymi, w których klasa T pojawia się więcej niż raz). Innymi słowy, oznacza to, że jeśli jest to upcast, rób to statycznie, bez mechanizmu run-time (unikając potencjalnej awarii, gdzie nie powinno się to dziać). W tym przypadku kompilator powinien odrzucić rzutowanie na tych samych zasadach, co w przypadku wersji static_cast<T>(v).

W Punkcie (6) wyraźnie "inaczej" odnosi się bezpośrednio do Punktu (5) (i na pewno do trywialnego przypadku z Punktu (3)). Znaczenie (wraz z punktem (7)), że jeśli rzutowanie nie jest upcastem (a nie rzutem tożsamości) (punkt (3)), to jest rzutem w dół i powinno zostać rozwiązane w czasie wykonywania , z wyraźnym wymogiem, że typ (z) jest typem polimorficznym (ma funkcję wirtualną).

Twój kod powinien zostać odrzucony przez zgodny ze standardami kompilator. Dla mnie nie ma co do tego wątpliwości. Ponieważ obsada jest odlewana, a typ v nie jest polimorficzny. Nie spełnia wymagań określonych w normie. Klauzula pustego wskaźnika (punkt (4)) nie ma nic wspólnego z tym, czy jest to zaakceptowany kod, czy nie, ma to tylko związek z zachowaniem pustej wartości wskaźnika w całym obsadzie (w przeciwnym razie niektóre implementacje mogą spowodować (głupie)), aby nadal stosować przesunięcie wskaźnika rzutowania, nawet jeśli wartość jest pusta).

Oczywiście, mogli oni dokonać innego wyboru i zezwolili obsadzie na zachowanie się jako rzut statyczny od podstawy do wyprowadzenia (tj. Bez sprawdzenia w czasie wykonywania), gdy typ podstawowy nie jest polimorficzny, ale Sądzę, że łamie to semantykę dynamicznej obsady, która wyraźnie mówi "chcę sprawdzić w czasie wykonywania tej obsady", w przeciwnym razie nie używałbyś dynamicznej obsady!

+0

To zostało już zasugerowane, ale "inaczej" ma zawierać punkt 3. Nawet twoje wyjaśnienie zakłada, że ​​tak. Ponadto w Standardzie istnieje wiele miejsc, w których interpretacja jako "else if ... else if ... else" jest absolutnie poprawna. I twoje ostatnie zdanie dotyczące przypadku (5) jest błędne, kompilator nie odrzucił 'static_cast (x)'. –

+0

@Ben Voigt: Chodziło mi o to (w punkcie (5)), że powinien ocenić ważność obsady na podstawie tych samych kryteriów co rzut statyczny, nie miałem na myśli, że twój konkretny przykład powinien zostać odrzucony, oczywiście, że nie. –

+0

Ach, ok, ta część ma sens, gdy zostanie to wyjaśnione w ten sposób. Ale traktowanie "inaczej" jako "innego niż 5" wciąż nie udaje się, ponieważ jest sprzeczne z 3. –

Powiązane problemy