Gdybym chciał byś złośliwy, to stwierdziłbym, że w C++ nawet ‘nic’ (czyli NULL
) nie jest takie, jak być powinno. Ale ponieważ w rzeczywistości jestem wcieleniem łagodności (;]), napiszę raczej o tym, jak można zaradzić na niedogodności obecnej postaci wskaźnika pustego w C++.
Cały problem z NULL
-em polega na tym, że nie jest on wartością odpowiedniego typu. Dwie klasyczne definicje tej stałej – jako 0
lub (void*)0
– mają zauważalne mankamenty. Pierwsza definiuje NULL
jako liczbę, co sprawia, że dozwolone są bezsensowne podstawienia w rodzaju:
Natomiast druga nie da się wprawdzie skonwertować na typ liczbowy, ale nie da się też niejawnie zmienić w wartość żadnego innego typu wskaźnikowego niż void*
. A tego oczekiwalibyśmy po wskaźniku pustym. Dlatego z dwojga złego w standardzie przyjęto pierwszą definicję i NULL
jest po prostu zerem.
To, czego naprawdę byśmy chcieli po NULL
, to wartość 0
, która:
To zaś da się osiągnąć, pisząc odpowiednią… klasę:
Sztuczką są tu oczywiście szablony operatorów konwersji. Zapewniają one możliwość traktowania naszego NULL
-a jako wartości dowolnego typu wskaźnikowego – łącznie ze wskaźnikami do składowych klas. A ze względu na brak innych konwersji, nie jest możliwe automatycznie przypisanie naszego pustego wskaźnika do zmiennej liczbowej.
Taki NULL
jest na tyle dobry, że działa nawet z uchwytami Windows API, gdyż wewnętrznie są one zdefiniowane jako specyficzne wskaźniki. Dziwi więc, dlaczego nadal nie ma go chociażby w bibliotece standardowej C++. Najwyraźniej nawet jeśli chcemy mieć ‘nic’, trzeba się trochę napracować ;P
Jednym z powodów dla których uwielbiam C/C++ i nie znoszę Pascala jest ścisła kontrola typów.
Brak takiej ścisłej kontroli w C/C++ daje programiście ogromne możliwości. Naturalnie razem z możliwościami na barki programisty zrzucana jest także odpowiedzialność za działanie i czytelność.
Stąd – w Pascalu może pisać każdy, bo jeśli napisze działający program to da się go później przeczytać i zrozumieć bez większych problemów. Jeśli chodzi o kod w C++ to tutaj doświadczony programista stworzy ładny kod, który potrafi odczytać tylko inny doświadczony programista ;) To piękne :)
Właściwie nie odniosłem się jawnie do notki więc daję sprostowanie:
Implementowanie w ten sposób NULL’a to raczej programistyczna ciekawostka, bo przydatność tego jest raczej mierna ;)
Sztuka dla sztuki? :)
Niekoniecznie. Jeśli na przykład ktoś nieopatrznie przeciąży dwie funkcje dla typu liczbowego wskaźnikowego:
to przy zwykłym NULL-u wywołania:
będą dotyczyły tej samej wersji funkcji – pierwszej, chociaż za drugim razem pewnie chodziło nam o wersję wskaźnikową. Podana wyżej definicja NULL-a, chociaż niedoskonała (jest przynajmniej jedna rzecz, którą można w niej poprawić; można zgłaszać pomysły :)), zabezpieczy przed taką sytuacją. Inna sprawa, że nie powinno się robić takich przeciążeń, ale co nie jest zabronione przez kompilator…
Zasadniczo problem sprowadza się do tego, że o ile C# ma null
, Delphi ma nil
, a VB Nothing
, to C++ nie ma wbudowanego w język wskaźnika pustego. W C++0x będzie aczkolwiek nullptr
.
Czyli jednak sztuka dla sztuki…
tzn. sztuka pie* o niczym ;p
pozdr. :)
@Xion: z ogromną elastycznością łączy się wiele niebezpieczeństw. Jeśli ktoś zrobi taki błąd to znaczy, że powinien zająć się ogrodnictwem :P
W C/C++ po prostu trzeba umieć programować ;)
@moriturius: a co gdy NULL tak na prawdę nie jest NULLem ? Np. przy wielokrotnym dziedziczeniu.
@agent_J: a w jaki sposob NULL moze nie byc NULLem? :P nawet przy wielokrotnym dziedziczeniu?
@agent_J: przy wielokrotnym dziedziczeniu static_cast sprawdza, czy przypadkiem wskaźnik to nie 0. Jeśli nie, to deltę dodaje. Jak tak, to zwraca niezmieniony.