Что именно является nullptr?
Теперь у нас есть C++11 со многими новыми функциями. Интересным и запутанным (по крайней мере для меня) является новый nullptr
,
Ну, не надо больше для мерзкого макроса NULL
,
int* x = nullptr;
myclass* obj = nullptr;
Тем не менее, я не понимаю, как nullptr
работает. Например, статья в Википедии гласит:
C++11 исправляет это, вводя новое ключевое слово, служащее отличительной константой нулевого указателя: nullptr. Он имеет тип nullptr_t, который неявно конвертируем и сопоставим с любым типом указателя или указателем на член. Он не является неявно конвертируемым или сопоставимым с целочисленными типами, кроме bool.
Как это ключевое слово и экземпляр типа?
Кроме того, у вас есть еще один пример (кроме Википедии), где nullptr
превосходит старое доброе 0
?
11 ответов
Как это ключевое слово и экземпляр типа?
Это не удивительно. И то и другое true
а также false
являются ключевыми словами и в качестве литералов они имеют тип (bool
). nullptr
является литералом-указателем типа std::nullptr_t
и это значение (вы не можете взять его адрес, используя &
).
4.10
о преобразовании указателя говорит, что тип значенияstd::nullptr_t
является константой нулевого указателя, и что интегральная константа нулевого указателя может быть преобразована вstd::nullptr_t
, Противоположное направление не допускается. Это позволяет перегрузить функцию как для указателей, так и для целых чисел, и передатьnullptr
выбрать версию указателя. ПереходяNULL
или же0
смущенно выбрал быint
версия.Актерский состав
nullptr_t
к интегральному типу нуженreinterpret_cast
и имеет ту же семантику, что и актерский состав(void*)0
к целочисленному типу (определена реализация отображения).reinterpret_cast
не может конвертироватьnullptr_t
к любому типу указателя. Положитесь на неявное преобразование, если это возможно, или используйтеstatic_cast
,Стандарт требует, чтобы
sizeof(nullptr_t)
бытьsizeof(void*)
,
Почему nullptr в C++11? Что это? Почему NULL недостаточно?
Эксперт по С ++ Алекс Аллен прекрасно говорит здесь:
"... представьте, что у вас есть следующие два объявления функций:
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, чтобы иметь особое значение при использовании в качестве указателя."
От nullptr: типобезопасный и четкий нулевой указатель:
Новое ключевое слово C++09 nullptr обозначает константу rvalue, которая служит универсальным литералом нулевого указателя, заменяя глючный и слабо типизированный литерал 0 и печально известный макрос NULL. Таким образом, nullptr положил конец более чем 30 годам смущения, двусмысленности и ошибок. В следующих разделах представлено средство nullptr и показано, как оно может исправить недуги NULL и 0.
Другие ссылки:
- WikiBooks, с примером кода.
- Здесь, в переполнении стека: вы используете NULL или 0 (ноль) для указателей в C++?
template
- Группа Google: comp.lang.C++. Moderated - обсуждение компилятора
Если у вас есть функция, которая может получать указатели на несколько типов, вызывая ее с помощью 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
и pointer
как NULL
это макрос, расширенный до 0
который может служить как начальным значением для int
также как и pointer
,
Кроме того, у вас есть еще один пример (кроме Википедии), где
nullptr
превосходит старый добрый 0?
Да. Это также (упрощенный) реальный пример, который произошел в нашем производственном коде. Он выделялся только потому, что gcc смог выдать предупреждение при кросс-компиляции на платформу с другой шириной регистра (все еще не уверен, почему именно при кросс-компиляции с x86_64 до x86, предупреждает warning: converting to non-pointer type 'int' from NULL
):
Рассмотрим этот код (C++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
ложные
С другой стороны, в простой C, NULL == 0
вернул бы истинный IIRC, потому что NULL
это просто макрос, возвращающий 0, который всегда является неверным адресом (AFAIK).
Позвольте мне сначала дать вам простую реализацию nullptr_t
struct nullptr_t
{
void operator&() const = delete; // Can't take address of nullptr
template<class T>
inline operator T*() const { return 0; }
template<class C, class T>
inline operator T C::*() const { return 0; }
};
nullptr_t nullptr;
nullptr
является тонким примером идиомы возвращаемого типа Resolver для автоматического вывода нулевого указателя правильного типа в зависимости от типа экземпляра, которому он присваивается.
int *ptr = nullptr; // OK
void (C::*method_ptr)() = nullptr; // OK
- Как вы можете сказать выше, когда
nullptr
присваивается целочисленному указателю,int
создание экземпляра типизированной функции преобразования. То же самое касается указателей на методы. - Таким образом, используя функциональность шаблона, мы фактически создаем соответствующий тип нулевого указателя каждый раз, когда мы это делаем, присвоение нового типа.
- Как
nullptr
является целочисленным литералом со значением ноль, вы не можете использовать его адрес, что мы сделали, удалив оператор &.
Зачем нам нужен nullptr
на первом месте?
- Вы видите традиционные
NULL
имеет некоторую проблему, как показано ниже:
1️⃣ Неявное преобразование
char *str = NULL; // Implicit conversion from void * to char *
int i = NULL; // OK, but `i` is not pointer type
2️⃣ Неопределенность вызова функции
void func(int) {}
void func(int*){}
void func(bool){}
func(NULL); // Which one to call?
- Компиляция выдает следующую ошибку:
error: call to 'func' is ambiguous
func(NULL);
^~~~
note: candidate function void func(bool){}
^
note: candidate function void func(int*){}
^
note: candidate function void func(int){}
^
1 error generated.
compiler exit status 1
3️⃣ Перегрузка конструктора
struct String
{
String(uint32_t) { /* size of string */ }
String(const char*) { /* string */ }
};
String s1( NULL );
String s2( 5 );
- В таких случаях вам нужно явное приведение (т. Е.
String s((char*)0))
.
Это ключевое слово, потому что стандарт будет указывать его как таковой.;-) Согласно последнему публичному проекту (n2914)
2.14.7 Литералы указателя [lex.nullptr]
pointer-literal: nullptr
Указатель литерал является ключевым словом
nullptr
, Это значение типаstd::nullptr_t
,
Это полезно, поскольку неявно преобразуется в целочисленное значение.
Допустим, у вас есть функция (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*)
}
Раньше 0 было единственным целочисленным значением, которое могло использоваться в качестве инициализатора без приведения для указателей: вы не можете инициализировать указатели с другими целочисленными значениями без приведения. Вы можете рассматривать 0 как одноэлементное выражение, синтаксически похожее на целочисленный литерал. Он может инициировать любой указатель или целое число. Но удивительно, вы обнаружите, что он не имеет четкого типа: это int
, Так почему же 0 может инициализировать указатели, а 1 - нет? Практический ответ состоял в том, что нам нужны средства определения нулевого значения указателя и прямого неявного преобразования int
указатель подвержен ошибкам. Таким образом 0 стал настоящим чудовищным чудаком из доисторической эпохи.nullptr
было предложено быть реальным одноэлементным представлением constexpr нулевого значения для инициализации указателей. Его нельзя использовать для непосредственной инициализации целых чисел и устранения неопределенностей, связанных с определением NULL
с точки зрения 0. nullptr
может быть определена как библиотека с использованием синтаксиса std, но семантически выглядит как отсутствующий основной компонент.NULL
в настоящее время устарела в пользу nullptr
, если какая-то библиотека не решит определить его как nullptr
,
Согласно cppreference,
nullptr
ключевое слово, которое:
обозначает литерал указателя. Это значение типа
std::nullptr_t
. Существуют неявные преобразования из nullptr в значение нулевого указателялюбого типа указателя и любого указателя на тип члена. Подобные преобразования существуют для любой константы нулевого указателя, которая включает значения типаstd::nullptr_t
а также макросNULL
.
nullptr
будет неявно преобразовывать в любой тип указателя, но не в целое число.
NULL
однако это макрос и константа нулевого указателя, определяемая реализацией. Часто это определяется так:
#define NULL 0
т.е. целое число.
Отсюда:
int i = NULL; //OK
int i = nullptr; //error
int* p = NULL; //OK
int* p = nullptr; //OK
nullptr
можно избежать двусмысленности, если у вас есть две перегрузки функций, например:
void func(int x); //1)
void func(int* x); //2)
func(NULL)
вызывает 1) потому что
NULL
целое число.func(nullptr)
вызывает 2) потому что
nullptr
не является целым числом и неявно преобразуется в любой тип указателя.
Преимущества использования nulptr:
- избегать двусмысленности между перегрузками функций
- позволяет вам делать специализацию шаблона
- более безопасный, интуитивно понятный и выразительный код, например
if (ptr == nullptr)
вместоif (ptr == 0)
Вот заголовок 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 в обычные указатели в качестве значений часовых, очевидно, уменьшит фактор "никогда не забывай, это может быть бомба".
NULL не обязательно должен быть 0. Пока вы используете всегда NULL и никогда не 0, NULL может принимать любое значение. Предполагается, что вы программируете микроконтроллер von Neuman с плоской памятью, у которого вектор прерываний равен 0. Если NULL равен 0 и что-то записывает в нулевой указатель, микроконтроллер выходит из строя. Если NULL, скажем, 1024, а в 1024 есть зарезервированная переменная, запись не приведет к сбою, и вы можете обнаружить назначения NULL Pointer изнутри программы. Это бессмысленно для ПК, но для космических зондов, военной или медицинской техники важно не разбиться.