ядро языка · синтаксисcore language · syntax

nullptr

Литерал нулевого указателя со своим собственным типом std::nullptr_t. Убирает неоднозначность между «нулевым указателем» и «целым нулём», которая жила в языке со времён 0 и макроса NULL.A null pointer literal with its own type, std::nullptr_t. It removes the ambiguity between "a null pointer" and "the integer zero" that had lived in the language since the days of 0 and the NULL macro.

часто используетсяcommonly used типобезопасностьtype safety

Что этоWhat it is

nullptr — литерал типа std::nullptr_t, неявно конвертируемый в любой тип указателя (и указатель на член), но не конвертируемый неявно в целочисленные типы (кроме bool). До C++11 роль нулевого указателя играли 0 или макрос NULL (обычно определяемый как 0 или 0L), которые остаются целыми числами по типу.nullptr is a literal of type std::nullptr_t, implicitly convertible to any pointer type (and pointer-to-member), but not implicitly convertible to integer types (except bool). Before C++11, the role of a null pointer was played by 0 or the NULL macro (typically defined as 0 or 0L), both of which remain integers by type.

до C++11before C++11
void f(int);
void f(char*);

f(NULL);   // NULL == 0 → выбирает f(int)!// NULL == 0 → picks f(int)!
           // неожиданно для читателя кода// surprising to anyone reading the code
с nullptrwith nullptr
void f(int);
void f(char*);

f(nullptr); // однозначно f(char*) —// unambiguously f(char*) —
            // nullptr не конвертируется в int// nullptr doesn't convert to int

Зачем ввелиWhy it was introduced

NULL как псевдоним нуля создавал реальные проблемы в перегрузке и generic-коде:NULL as an alias for zero created real problems for overload resolution and generic code:

01

Неоднозначная перегрузкаAmbiguous overload resolution

Вызов функции с NULL при наличии перегрузок по указателю и по целому типу выбирал целочисленную версию — вопреки очевидному намерению программиста передать указатель.Calling a function with NULL when overloads existed for both a pointer and an integer type would pick the integer version — despite the programmer's obvious intent to pass a pointer.

02

NULL не работает в шаблонахNULL doesn't work in templates

В generic-коде, где тип параметра выводится через T, передача NULL выводит T = int (или long), а не тип указателя — код, ожидающий указатель, ломается тихо, без ошибки компиляции в очевидном месте.In generic code where the parameter type is deduced as T, passing NULL deduces T = int (or long), not a pointer type — code expecting a pointer breaks silently, with no compile error at an obvious location.

03

Читаемость и намерение в кодеReadability and clarity of intent

if (ptr == nullptr) однозначно говорит о сравнении с нулевым указателем; if (ptr == 0) или if (ptr == NULL) требует знать контекст типа ptr, чтобы понять, что это не сравнение с целым нулём.if (ptr == nullptr) unambiguously states a comparison against a null pointer; if (ptr == 0) or if (ptr == NULL) requires knowing the type of ptr to realize it isn't a comparison against the integer zero.

Как это работает под капотомHow it works under the hood

std::nullptr_t — отдельный встроенный тип языка (не typedef и не макрос), определённый в <cstddef>. Компилятор допускает неявное преобразование значения этого типа в любой тип указателя или указатель-на-член, но не допускает неявного преобразования в целочисленный тип — только явный static_cast. Физически nullptr при компиляции превращается в тот же машинный ноль-адрес, что и раньше — рантайм-представление не меняется, меняются только правила разрешения перегрузок и вывода типов на этапе компиляции.std::nullptr_t is a distinct built-in language type (not a typedef, not a macro), defined in <cstddef>. The compiler allows an implicit conversion from a value of this type to any pointer type or pointer-to-member, but disallows an implicit conversion to an integer type — only an explicit static_cast works there. Physically, nullptr still compiles down to the same all-zero machine address as before — the runtime representation hasn't changed; only the compile-time overload resolution and type deduction rules have.

void f(int);
void f(char*);

f(nullptr);  // f(char*) — однозначно, nullptr_t → char* есть,// f(char*) — unambiguous, nullptr_t → char* exists,
             // nullptr_t → int нет// nullptr_t → int doesn't

int n = nullptr;          // ошибка компиляции — нет неявного nullptr_t → int// compile error — no implicit nullptr_t → int
int* p = nullptr;         // ок// ok
bool b = nullptr == p;    // ок — сравнение указателей, bool разрешён// ok — pointer comparison, bool is allowed

ГраблиGotchas

Старый код с NULL/0 не ломается, но не выигрываетOld code using NULL/0 doesn't break, but doesn't benefit either

NULL и 0 остаются валидными указателями на ноль — компилятор их не запрещает. Миграция на nullptr — вопрос дисциплины, а не принуждения языком; смешанный код (местами NULL, местами nullptr) компилируется без предупреждений.NULL and 0 remain valid null pointer values — the compiler doesn't forbid them. Migrating to nullptr is a matter of discipline, not something the language enforces; mixed code (NULL in some places, nullptr in others) compiles without warnings.

sizeof(nullptr) не всегда равенisn't always equal to sizeof(void*)

Стандарт не требует, чтобы std::nullptr_t имел тот же размер, что указатель — на практике почти всегда совпадает, но полагаться на это в коде, чувствительном к размеру (например, память под union), не стоит без static_assert.The standard doesn't require std::nullptr_t to have the same size as a pointer — in practice it almost always does, but relying on that in size-sensitive code (say, memory layout for a union) without a static_assert is asking for trouble.

nullptr в контексте bool — неявное преобразование разрешеноin a bool context — the implicit conversion is allowed

if (nullptr) компилируется и всегда ложно — преобразование nullptr_t → bool разрешено стандартом наравне с преобразованием указателя в bool. Не спасает от логической ошибки, если код случайно проверяет константу вместо переменной.if (nullptr) compiles and is always false — the nullptr_t → bool conversion is permitted by the standard on par with converting a pointer to bool. It won't save you from a logic error where the code accidentally checks a constant instead of a variable.