Что такое nullptr?

Теперь у нас есть С++ 11 со многими новыми функциями. Интересным и запутанным (по крайней мере для меня) является новый nullptr.

Ну, больше не нужно для неприятного макроса NULL.

int* x = nullptr;
myclass* obj = nullptr;

Тем не менее, я не понимаю, как работает nullptr. Например, Статья в Википедии говорит:

С++ 11 исправляет это, введя новое ключевое слово , чтобы служить в качестве выделенной константы нулевого указателя: nullptr. Он имеет тип тип nullptr_t, который неявно конвертируется и сопоставим с любым типом указателя или типом указателя на член. Он не является неявно конвертируемым или сопоставимым с целыми типами, за исключением bool.

Как это ключевое слово и экземпляр типа?

Кроме того, есть ли у вас другой пример (помимо Википедии), где nullptr превосходит старый добрый 0?

Ответы

Ответ 1

Как это ключевое слово и экземпляр типа?

Это не удивительно. Оба true и false являются ключевыми словами, а в качестве литералов у них есть тип (bool). nullptr - это литерал-указатель типа std::nullptr_t, и это prvalue (вы не можете взять его адрес с помощью &).

  • 4.10 о преобразовании указателя говорит, что prvalue типа std::nullptr_t является константой нулевого указателя и что интегральная константа нулевого указателя может быть преобразована в std::nullptr_t. Противоположное направление не допускается. Это позволяет перегрузить функцию как для указателей, так и для целых чисел и передать nullptr для выбора версии указателя. Передача NULL или 0 будет путать версию int.

  • Приведение типа nullptr_t к интегральному типу требует a reinterpret_cast и имеет ту же семантику, что и приведение (void*)0 к интегральному типу (определенная реализация реализации). A reinterpret_cast не может преобразовать nullptr_t в любой тип указателя. По возможности используйте неявное преобразование или используйте static_cast.

  • Стандарт требует, чтобы sizeof(nullptr_t) был sizeof(void*).

Ответ 2

От nullptr: типобезопасный и четкий нулевой указатель:

Новое ключевое слово C++ 09 nullptr обозначает константу rvalue, которая служит универсальным литералом нулевого указателя, заменяя глючный и слабо типизированный литерал 0 и печально известный макрос NULL. Таким образом, nullptr положил конец более чем 30 годам смущения, двусмысленности и ошибок. В следующих разделах представлено средство nullptr и показано, как оно может исправить недуги NULL и 0.

Другие ссылки:

Ответ 3

Если у вас есть функция, которая может получать указатели на более чем один тип, вызывать ее с NULL неоднозначно. То, как это обходится сейчас, очень хакерское, принимая int и предполагая, что оно NULL.

template <class T>
class ptr {
    T* p_;
    public:
        ptr(T* p) : p_(p) {}

        template <class U>
        ptr(U* u) : p_(dynamic_cast<T*>(u)) { }

        // Without this ptr<T> p(NULL) would be ambiguous
        ptr(int null) : p_(NULL)  { assert(null == NULL); }
};

В C++11 вы сможете перегружать nullptr_t так что ptr<T> p(42); будет ошибка времени компиляции, а не во время выполнения assert.

ptr(std::nullptr_t) : p_(nullptr)  {  }

Ответ 4

Почему nullptr в C++ 11? Что это? Почему NULL недостаточно?

C++ эксперт Алекс Аллен прекрасно говорит здесь:

"... представьте, что у вас есть следующие два объявления функций:

void func(int n); 
void func(char *s);

func( NULL ); // guess which function gets called?

Хотя похоже, что будет вызвана вторая функция - в конце концов, вы передаете то, что кажется указателем - это действительно первая функция, которая будет вызвана! Проблема в том, что, поскольку NULL равен 0, а 0 - целое число, вместо него будет вызвана первая версия func. Это такая вещь, которая, да, не случается все время, но когда это происходит, это чрезвычайно расстраивает и сбивает с толку. Если вы не знаете подробностей происходящего, это может выглядеть как ошибка компилятора. Функция языка, которая выглядит как ошибка компилятора, ну, не то, что вам нужно.

Введите nullptr. В C++ 11 nullptr - это новое ключевое слово, которое может (и должно!) Использоваться для представления указателей NULL; другими словами, где бы вы ни писали ранее NULL, вы должны использовать вместо него nullptr. Это не более понятно для вас, программист, (все знают, что означает NULL), но это более явно для компилятора, который больше не будет видеть везде, где используются 0, чтобы иметь особое значение при использовании в качестве указателя. "

Ответ 5

nullptr не может быть присвоен целочисленному типу, такому как int но только типу указателя; либо встроенный тип указателя, такой как int *ptr либо интеллектуальный указатель, такой как std::shared_ptr<T>

Я считаю, что это важное различие, поскольку NULL все еще может быть назначен как целочисленному типу, так и указателю, поскольку NULL - это макрос, расширенный до 0 который может служить как начальным значением для int так и указателем.

Ответ 6

Ну, на других языках зарезервированы слова, которые являются экземплярами типов. Python, например:

>>> None = 5
  File "<stdin>", line 1
SyntaxError: assignment to None
>>> type(None)
<type 'NoneType'>

Это на самом деле довольно близкое сравнение, потому что None обычно используется для чего-то, что не было инициализировано, но в то же время сравнения, такие как None == 0, являются ложными.

С другой стороны, в plain C, NULL == 0 возвращает true IIRC, потому что NULL - это просто макрос, возвращающий 0, который всегда является недопустимым адресом (AFAIK).

Ответ 7

Кроме того, у вас есть другой пример (помимо Википедии), где nullptr превосходит старые добрые 0?

Да. Это также (упрощенный) реальный пример, который произошел в нашем производственном коде. Он только выделялся, потому что gcc смог выдать предупреждение при перекрестном соединении с платформой с различной шириной регистра (все еще не уверен точно, почему только при перекрещивании с x86_64 на x86 предупреждает warning: converting to non-pointer type 'int' from NULL):

Рассмотрим этот код (С++ 03):

#include <iostream>

struct B {};

struct A
{
    operator B*() {return 0;}
    operator bool() {return true;}
};

int main()
{
    A a;
    B* pb = 0;
    typedef void* null_ptr_t;
    null_ptr_t null = 0;

    std::cout << "(a == pb): " << (a == pb) << std::endl;
    std::cout << "(a == 0): " << (a == 0) << std::endl; // no warning
    std::cout << "(a == NULL): " << (a == NULL) << std::endl; // warns sometimes
    std::cout << "(a == null): " << (a == null) << std::endl;
}

Он выводит этот результат:

(a == pb): 1
(a == 0): 0
(a == NULL): 0
(a == null): 1

Ответ 8

Это ключевое слово, потому что стандарт укажет его как таковой.;-) Согласно последнему публичному проекту (n2914)

2.14.7 Литералы указателя [lex.nullptr]

pointer-literal:
nullptr

Литералом указателя является ключевое слово nullptr. Это значение типа std::nullptr_t.

Это полезно, потому что он не неявно преобразуется в интегральное значение.

Ответ 9

Допустим, у вас есть функция (f), которая перегружена и принимает как int, так и char *. До C++ 11, если вы хотели вызвать его с нулевым указателем и использовали NULL (т.е. Значение 0), вы бы вызвали перегруженный для int:

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

void g() 
{
  f(0); // Calls f(int).
  f(NULL); // Equals to f(0). Calls f(int).
}

Это, вероятно, не то, что вы хотели. C++ 11 решает это с помощью nullptr; Теперь вы можете написать следующее:

void g()
{
  f(nullptr); //calls f(char*)
}

Ответ 10

Раньше 0 было единственным целочисленным значением, которое могло использоваться в качестве инициализатора без приведения для указателей: вы не можете инициализировать указатели с другими целочисленными значениями без приведения. Вы можете рассматривать 0 как одноэлементное выражение, синтаксически похожее на целочисленный литерал. Может инициировать любой указатель или целое число. Но удивительно, что вы обнаружите, что он не имеет определенного типа: это int. Так почему же 0 может инициализировать указатели, а 1 - нет? Практический ответ состоял в том, что нам нужны средства определения нулевого значения указателя, и прямое неявное преобразование int в указатель подвержено ошибкам. Таким образом 0 стал настоящим чудовищным чудаком из доисторической эпохи. nullptr было предложено, чтобы быть реальными одноточечен constexpr представления нулевого значения для инициализации указателей. Его нельзя использовать для непосредственной инициализации целых чисел и устранения неопределенностей, связанных с определением NULL в терминах 0. nullptr может быть определен как библиотека с использованием синтаксиса std, но семантически выглядит как отсутствующий основной компонент. NULL теперь считается устаревшим в пользу nullptr, если какая-то библиотека не решит определить его как nullptr.

Ответ 11

Здесь заголовок LLVM.

// -*- C++ -*-
//===--------------------------- __nullptr --------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP_NULLPTR
#define _LIBCPP_NULLPTR

#include <__config>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif

#ifdef _LIBCPP_HAS_NO_NULLPTR

_LIBCPP_BEGIN_NAMESPACE_STD

struct _LIBCPP_TEMPLATE_VIS nullptr_t
{
    void* __lx;

    struct __nat {int __for_bool_;};

    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t() : __lx(0) {}
    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t(int __nat::*) : __lx(0) {}

    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR operator int __nat::*() const {return 0;}

    template <class _Tp>
        _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
        operator _Tp* () const {return 0;}

    template <class _Tp, class _Up>
        _LIBCPP_INLINE_VISIBILITY
        operator _Tp _Up::* () const {return 0;}

    friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator==(nullptr_t, nullptr_t) {return true;}
    friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator!=(nullptr_t, nullptr_t) {return false;}
};

inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t __get_nullptr_t() {return nullptr_t(0);}

#define nullptr _VSTD::__get_nullptr_t()

_LIBCPP_END_NAMESPACE_STD

#else  // _LIBCPP_HAS_NO_NULLPTR

namespace std
{
    typedef decltype(nullptr) nullptr_t;
}

#endif  // _LIBCPP_HAS_NO_NULLPTR

#endif  // _LIBCPP_NULLPTR

(многое можно найти с помощью быстрого grep -r/usr/include/*')

Одна вещь, которая выскакивает, это перегрузка оператора * (возвращение 0 намного удобнее, чем segfaulting...). Другое дело, что он не выглядит совместимым с хранением адреса вообще. Что, по сравнению с тем, как оно обрабатывает void * и передачу результатов NULL в обычные указатели в качестве значений часового, очевидно, уменьшит фактор "никогда не забывай, это может быть бомба".

Ответ 12

NULL не обязательно должно быть 0. Поскольку вы всегда используете NULL и никогда не 0, NULL может быть любым значением. Предполагая, что вы программируете микроконтроллер von Neuman с плоской памятью, у которого есть свои прерывающие векторы в 0. Если NULL равно 0, а что-то пишет с помощью указателя NULL, микроконтроллер падает. Если NULL позволяет указывать 1024, а 1024 - зарезервированная переменная, запись не приведет к ее краху, и вы можете обнаружить назначения NULL Pointer изнутри программы. Это бессмысленно на ПК, но для космических зондов, военного или медицинского оборудования важно не сбой.