Что именно является nullptr?

468

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

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

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

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

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

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

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

  • 22
    связанный с этим факт: nullptr также используется для представления нулевой ссылки для управляемых дескрипторов в C ++ / CLI.
  • 3
    При использовании Visual C ++ помните, что если вы используете nullptr с собственным кодом C / C ++, а затем компилируете с параметром компилятора / clr, компилятор не может определить, указывает ли nullptr собственное или управляемое значение нулевого указателя. Чтобы сделать ваше намерение понятным для компилятора, используйте nullptr для указания управляемого значения или __nullptr для указания собственного значения. Microsoft реализовала это как расширение компонента.
Показать ещё 4 комментария
Теги:
pointers
c++11
nullptr

9 ответов

347
Лучший ответ

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

Это не удивительно. Оба 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*).

  • 0
    О, после просмотра мне кажется, что условный оператор не может преобразовать 0 в nullptr в таких случаях, как cond ? nullptr : 0; , Удалено из моего ответа.
  • 79
    Обратите внимание, что NULL даже не гарантируется равным 0 . Это может быть 0L , в этом случае вызов void f(int); void f(char *); будет неоднозначным. nullptr всегда предпочитает версию указателя и никогда не вызывает int . Также обратите внимание, что nullptr можно преобразовать в bool (в проекте написано, что в 4.12 ).
Показать ещё 6 комментариев
46

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

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

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

  • 14
    C ++ 09? Разве это не упоминалось как C ++ 0x до августа 2011 года?
  • 2
    @anthropomorphic Ну, это его цель. C ++ 0x использовался, пока он еще находился в стадии разработки, потому что не было известно, будет ли он закончен в 2008 или 2009 году. Обратите внимание, что на самом деле он стал C ++ 0B, что означает C ++ 11. Смотрите stroustrup.com/C++11FAQ.html
26

Когда у вас есть функция, которая может получать указатели более чем на один тип, тогда вызов с помощью 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)  {  }
6

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

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

3

Кроме того, у вас есть другой пример (помимо Википедии), где 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
  • 0
    Я не вижу, как это улучшается при использовании nullptr (и C ++ 11). Если вы установите для pb значение nullptr, первое сравнение оценивается как истинное (при сравнении яблок с грушами ...). Второй случай еще хуже: если вы сравните a с nullptr, он преобразует a в B *, а затем снова оценивается как true (до того, как он был приведен к bool, а expr оценен как false). Все это напоминает мне JavaScript, и мне интересно, получим ли мы === в C ++ в будущем :(
3

Ну, на других языках зарезервированы слова, которые являются экземплярами типов. 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).

  • 4
    NULL - это макрос, который расширяется до нуля, константный ноль, приведенный к указателю, создает нулевой указатель. Нулевой указатель не обязательно должен быть нулем (но часто это так), ноль не всегда является недопустимым адресом, и непостоянный ноль, приведенный к указателю, не должен быть нулевым, а нулевой указатель приведен к целое число не должно быть нулем. Надеюсь, я все понял, ничего не забыв. Ссылка: c-faq.com/null/null2.html
  • 0
    AFAIK IIRC! = NULL.
3

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

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

pointer-literal:
nullptr

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

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

1

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

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

void g() 
{
  f(0); //calls f(int)
}

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

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

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

  • 0
    Что ж, фактическое значение нулевого указателя в памяти может не быть нулевым, но стандарт C (и C ++) обязывает компиляторы преобразовывать целочисленный литерал 0 в нулевой указатель.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню