Wskaźnik pusty – nullptr.
Standard C++11 zajął się na poważnie jedną z kluczowych kwestii w kontekście języka – oznaczaniem wskaźników pustych (nieważnych). Nowościami są tutaj słowo kluczowe nullptr, oznaczające wskaźnik pusty oraz typ wskaźnika pustego, czyli std::nullptr_t.
nullptr jest literałem, który powinien być używany w kodzie źródłowym i oznacza wskaźnik pusty. Czym różni się on od wartości NULL, która wywodzi się z języka C i była do tej pory stosowana również w C++ ?
Po pierwsze: wskaźnik pusty posiada określony i zdefiniowany, osobny typ – std::nullptr_t. Jest on typem podstawowym, zdefiniowanym od wersji C++11 języka. Oficjalnie mamy więc taką linię w pliku nagłówkowym cstddef:
typedef decltype(nullptr) nullptr_t;
Po drugie: w przeciwieństwie do NULL, w przypadku nullptr nie nastąpi nigdy niejawna konwersja do typu całkowitego. W oprzypadku nullptr jest możliwa niejawna konwersja jedynie do typu wskaźnikowego lub typu bool. Rozwiązuje to raz na zawsze wiele niejasności, ale powoduje, że istniejący kod może powodować błędy kompilacji po prostej zamianie wszystkich wystąpień słowa NULL na nullptr.
Przykłady.
Porównajmy takie dwie linie kodu:
int i = NULL; int x = nullptr;
Linia nr 2. spowoduje błąd kompilacji, ponieważ nie może wystąpić nieajwna konwersja nullptr do typu całkowitego. Pierwsza linia nie powoduje problemów, ponieważ NULL jest najprawdopodobniej zdefiniowany literałem liczby całkowitej zero. Ale czy tak powinniśmy korzystać z wartości, która ma oznaczać wskaźnik pusty ?
Kolejny przykład:
void function(int a_Number); void function(const char* a_Pointer); function(NULL); // Którą wersję funckcji function wywołać ? function(nullptr); // Zostanie wywołana druga wersja funkcji function.
No i mamy problem z przeładowaniem nazwy funkcji. Naszą intencją jest wywołanie drugiej wersji funkcji – tej, która przyjmuje wskaźnik. Niestety z powodów podanych powyżej (i wyjaśnionych dokładniej poniżej), wartość NULL spowoduje wywołanie wersji funkcji function, która przyjmuje jako parametr wartość całkowitą. Co gorsza, na tym etapie nie spowoduje to żadnego błędu kompilacji.
Wyjaśnienia i ciekawostki.
Jak wiemy, wartość NULL była w języku C stałą, definiowana dyrektywą #define preprocesora, najczęściej na dwa sposoby:
#define NULL 0
lub
#define NULL (void*)0;
W starszych wersjach języka C++, stała NULL jest zdefiniowana tak, jak w pierwszym przykładzie powyżej. Jednak po włączeniu obsługi C++11 w ustawieniach kompilatora (coraz częściej jest to domyślny stan), możemy natkąć się na taką definicję:
#define NULL nullptr
Powyższa linia może spowodować brak kompatybilności wstecznej z wcześniejszymi wersjami języka w naszym kodzie i raczej nie powinna wystąpić w żadnej implementacji kompilatora, bez dodatkowych warunków. Takie problemy widzieliśmy powyżej, przy okazji omawiania przykładów.
Wnioski ?
Jeśli potrzebujemy użyć wyrażenia oznaczającego wskaźnik pusty, korzystajmy tylko i wyłącznie z nowego wskaźnika pustego 🙂
Pamiętajmy też o najważniejszych własnościach tego wskaźnika w C++11:
- może być konwertowany do dowolnego typu wskaźnikowego
- dozwolona jest niejawna konwersja do typu bool
- może być użyty w wyrażeniach warunkowych i porównywany ze wskaźnikami lub wartością całkowitą zero
- nie może być używany w wyrażeniach arytmetycznych
Kontynuuj poznawanie C++11: C++11 #5: Nawiasy kątowe a argumenty szablonów.
Dziękuję za ten bardzo zwarty i przejrzysty post o nullptr. Może mi się to szybko przydać.
Dawka użytecznej wiedzy, dzięki wielkie!
Cieszę się, że artykuł jest zrozumiały i pomocny 🙂