Od C++11 je k reprezentaci nulového ukazatele možno použít nové klíčové slovo nullptr
. V příspěvku se dozvíte, proč byste měli používat právě nullptr
místo NULL
či 0.
Proč se vyhýbat NULL či 0?
V dobách před C++11 se jako nulový ukazatel dala použít konstanta 0 nebo Céčkové makro NULL
. Ani jeden z těchto dvou přístupů však není ideální. Mějme následující dvě funkce:
#include <iostream> // kvůli std::cout #include <cstdlib> // kvůli NULL void f(int) { std::cout << "f(int)\n"; } void f(int *) { std::cout << "f(int *)\n"; }
Otázkou je, co vypíší následující dvě volání:
int main() { f(0); f(NULL); }
Odpověď vás možná překvapí, ale závisí to na překladači. Dle [C++14, 18.2 §3] (v C++98 a C++11 je situace stejná) má makro NULL
implementačně závislou hodnotu, kdy možné hodnoty jsou např. 0 či 0L
, ale rozhodně ne (void *)0
. Pokud je NULL
definováno jako 0, pak dojde k vypsání následujícího:
f(int) f(int)
Při pohledu na kód byste čekali, že druhé volání zavolá přetížení mající jako parametr ukazatel. Jak lze vidět, není tomu tak.
Ještě zajímavější to ale je, pokud je makro NULL
definováno jako 0L
(literál 0 typu long int
). Pak se kód ani nepřeloží:
file.cc: In function ‘int main()’: file.cc:14:8: error: call of overloaded ‘f(NULL)’ is ambiguous f(NULL); ^ file.cc:14:8: note: candidates are: file.cc:4:6: note: void f(int) void f(int) { ^ file.cc:8:6: note: void f(int*) void f(int *) { ^
Problém totiž je, že konverze long int
na int
je stejně drahá jako konverze long int
na int *
. Překladač tak má svázané ruce a nezbývá mu, než ohlásit chybu.
S problémem rozpoznání přetížené funkce úzce souvisí i dedukce parametrů u šablon. Pokud máte např.
template <typename T> void func(T p) { /* */ }
tak zavoláním
func(NULL)
se jako T
použije int
(či long int
nebo cokoliv jiného, co je typ hodnoty NULL
).
Další nepříjemností je, že následující kód v pohodě projde:
int i = NULL; // OK
Cože? "Ukazatel" na číslo? Ale projde to, protože NULL
je typicky 0 či 0L
, nikoliv ukazatel.
Záchrana: nullptr
C++11 přichází s řešením, kterým je nové klíčové slovo nullptr
. Jedná se o nulový ukazatel, který je typu std::nullptr_t
a je implicitně konvertovatelný na jakýkoliv jiný ukazatelový typ a na bool
:
char *pc = nullptr; // OK int *pi = nullptr; // OK bool b = nullptr; // OK (b je false)
To se hodí např. při testech, zda je či není ukazatel nulový:
int *p = g(); // Funkce g může vrátit nullptr. if (p) { // OK // ... }
Co je nejdůležitější, tak je, že následující kód
f(0); f(nullptr);
vypíše očekávaný výsledek:
f(int) f(int *)
Další výhoda je, že konverze na int
vyžaduje použít reinterpret_cast<>
:
int i = nullptr; // Chyba při překladu, nutnou použít reinterpret_cast<>.
Jak jsme viděli výše, int i = NULL;
by přes překladač prošlo bez chyby.
Závěrem
Pokud programujete pod C++11/14, určitě k reprezentaci nulového ukazatele používejte nullptr
místo NULL
či 0. Chování pak nebude závislé na překladači, bude se to chovat dle očekávání a kód bude čitelnější, než při použití konstanty 0.
Další čtení
Pokud by vás zajímaly detaily, mrkněte na následující odkazy.
- A name for the null pointer: nullptr - návrh
nullptr
do C++11 - More C++ Idioms/nullptr - ukázka implementace v C++98