Odpowiednie przypadki użycia dla wskaźników o wartości null to
- Przekierowanie do czegoś w rodzaju głębszego węzła drzewa, który może nie istnieć lub nie został jeszcze połączony. To jest coś, co należy zawsze ściśle zamknąć w dedykowanej klasie, więc czytelność lub zwięzłość nie stanowi tutaj większego problemu.
Dynamiczne rzuty. Rzutowanie wskaźnika klasy bazowej na konkretną klasę pochodną (coś, czego należy ponownie spróbować uniknąć, ale czasami może to okazać się konieczne) zawsze kończy się sukcesem, ale powoduje wyświetlenie wskaźnika o wartości null, jeśli klasa pochodna nie pasuje. Jednym ze sposobów sprawdzenia tego jest
Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr);
if(derived_ptr != nullptr) { ... }
(lub najlepiej auto derived_ptr = ...
). To jest złe, ponieważ pozostawia (prawdopodobnie nieprawidłowy, tj. Zerowy) wskaźnik pochodny poza if
zasięgiem bloku zabezpieczającego . Nie jest to konieczne, ponieważ C ++ pozwala na wprowadzenie zmiennych binarnych konwertowalnych wewnątrz if
-condition :
if(auto derived_ptr = dynamic_cast<Derived*>(base_ptr)) { ... }
który jest nie tylko krótszy i bezpieczny w zakresie, ale jest też znacznie bardziej przejrzysty w swoim celu: kiedy sprawdzasz wartość null w oddzielnym warunku if, czytelnik zastanawia się, „ok, więc derived_ptr
nie może być tutaj zero… cóż, dlaczego to jest zerowe? ” Podczas gdy wersja jednowierszowa mówi bardzo wyraźnie: „jeśli możesz bezpiecznie przesyłać base_ptr
do Derived*
, użyj go do ...”.
To samo działa równie dobrze w przypadku każdej innej operacji, która może spowodować niepowodzenie, która zwraca wskaźnik, chociaż IMO generalnie powinno się tego unikać: lepiej jest użyć czegoś takiego boost::optional
jak „kontener” dla wyników operacji, które mogą zakończyć się niepowodzeniem, zamiast wskaźników.
Tak więc, jeśli główny przypadek użycia wskaźników zerowych powinien być zawsze zapisywany w odmianie stylu implicit-cast, powiedziałbym, że ze względu na spójność dobrze jest zawsze używać tego stylu, tj. Byłbym zwolennikiem if(ptr)
ponad if(ptr!=nullptr)
.
Obawiam się, że muszę zakończyć reklamą: if(auto bla = ...)
składnia jest właściwie tylko nieco kłopotliwym przybliżeniem rzeczywistego rozwiązania takich problemów: dopasowywania wzorców . Dlaczego miałbyś najpierw wymusić jakąś akcję (na przykład rzucenie wskaźnika), a następnie rozważyć, że może wystąpić awaria ... To znaczy, to śmieszne, prawda? To tak, jakbyś miał coś do jedzenia i chciał zrobić zupę. Podajesz go asystentowi, który ma wycisnąć sok, jeśli jest to miękkie warzywo. Nie patrzysz na to najpierw. Kiedy masz ziemniaka, nadal dajesz go swojemu asystentowi, ale oni uderzają go z powrotem w twarz z notatką o niepowodzeniu. Ach, konieczne programowanie!
Znacznie lepiej: rozważ od razu wszystkie przypadki, które możesz napotkać. Następnie działaj odpowiednio. Haskell:
makeSoupOf :: Foodstuff -> Liquid
makeSoupOf p@(Potato{..}) = mash (boil p) <> water
makeSoupOf vegetable
| isSoft vegetable = squeeze vegetable <> salt
makeSoupOf stuff = boil (throwIn (water<>salt) stuff)
Haskell ma również specjalne narzędzia, gdy istnieje naprawdę poważna możliwość awarii (a także do całej masy innych rzeczy): monady. Ale to nie jest miejsce na ich wyjaśnianie.
⟨/ogłoszenie⟩
0
lubnullptr
. (NULL
jest C'ismem i wymaga dołączenia pliku nagłówkowego.)