Jste zde

Proč v C++ používat nullptr místo NULL či 0

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.

Přidat komentář