Теперь у нас есть С++ 11 со многими новыми функциями. Интересным и запутанным (по крайней мере для меня) является новый nullptr
.
Ну, больше не нужно для неприятного макроса NULL
.
int* x = nullptr;
myclass* obj = nullptr;
Тем не менее, я не понимаю, как работает nullptr
. Например, Статья в Википедии говорит:
С++ 11 исправляет это, введя новое ключевое слово , чтобы служить в качестве выделенной константы нулевого указателя: nullptr. Он имеет тип тип nullptr_t, который неявно конвертируется и сопоставим с любым типом указателя или типом указателя на член. Он не является неявно конвертируемым или сопоставимым с целыми типами, за исключением bool.
Как это ключевое слово и экземпляр типа?
Кроме того, есть ли у вас другой пример (помимо Википедии), где nullptr
превосходит старый добрый 0
?
Как это ключевое слово и экземпляр типа?
Это не удивительно. Оба 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*)
.
cond ? nullptr : 0;
, Удалено из моего ответа.
NULL
даже не гарантируется равным 0
. Это может быть 0L
, в этом случае вызов void f(int); void f(char *);
будет неоднозначным. nullptr
всегда предпочитает версию указателя и никогда не вызывает int
. Также обратите внимание, что nullptr
можно преобразовать в bool
(в проекте написано, что в 4.12
).
От nullptr: безопасный тип и четкий указатель нумерации:
Новое ключевое слово nullptr С++ 09 обозначает константу rvalue, которая служит в качестве универсального литерала нулевого указателя, заменяя багги и слабо типизированный литерал 0 и печально известный NULL-макрос. Таким образом, nullptr положит конец более чем 30-летнему смущению, двусмысленности и ошибкам. В следующих разделах представлен объект nullptr и показано, как он может исправить недомогания NULL и 0.
Другие ссылки:
template
Когда у вас есть функция, которая может получать указатели более чем на один тип, тогда вызов с помощью 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) { }
nullptr
не может быть присвоен integral type
, например int, но только тип pointer
; либо встроенный тип указателя, например int *ptr
, либо интеллектуальный указатель, например std::shared_ptr<T>
Я считаю, что это важное различие, поскольку NULL
все еще можно назначить integral type
и a pointer
, поскольку NULL
- это макрос, расширенный до 0
, который может служить как начальным значением для int
, а также pointer
.
Кроме того, у вас есть другой пример (помимо Википедии), где
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
Ну, на других языках зарезервированы слова, которые являются экземплярами типов. 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).
NULL
- это макрос, который расширяется до нуля, константный ноль, приведенный к указателю, создает нулевой указатель. Нулевой указатель не обязательно должен быть нулем (но часто это так), ноль не всегда является недопустимым адресом, и непостоянный ноль, приведенный к указателю, не должен быть нулевым, а нулевой указатель приведен к целое число не должно быть нулем. Надеюсь, я все понял, ничего не забыв. Ссылка: c-faq.com/null/null2.html
Это ключевое слово, потому что стандарт укажет его как таковой.;-) Согласно последнему публичному проекту (n2914)
2.14.7 Литералы указателя [lex.nullptr]
pointer-literal: nullptr
Литералом указателя является ключевое слово
nullptr
. Это значение типаstd::nullptr_t
.
Это полезно, потому что он не неявно преобразуется в интегральное значение.
Скажем, что у вас есть функция (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*)
}
NULL не обязательно должно быть 0. Поскольку вы всегда используете NULL и никогда не 0, NULL может быть любым значением. Предполагая, что вы программируете микроконтроллер von Neuman с плоской памятью, у которого есть свои прерывающие векторы в 0. Если NULL равно 0, а что-то пишет с помощью указателя NULL, микроконтроллер падает. Если NULL позволяет указывать 1024, а 1024 - зарезервированная переменная, запись не приведет к ее краху, и вы можете обнаружить назначения NULL Pointer изнутри программы. Это бессмысленно на ПК, но для космических зондов, военного или медицинского оборудования важно не сбой.
nullptr
также используется для представления нулевой ссылки для управляемых дескрипторов в C ++ / CLI.