Этот фрагмент кода концептуально делает то же самое для трех указателей (безопасная инициализация указателя):
int* p1 = nullptr;
int* p2 = NULL;
int* p3 = 0;
Итак, каковы преимущества назначения указателей nullptr
перед присвоением им значений NULL
или 0
?
int
иvoid *
не будет выбиратьint
версию надvoid *
версией при использованииnullptr
.f(nullptr)
отличается отf(NULL)
. Но что касается вышеприведенного кода (присваивания локальной переменной), все три указателя абсолютно одинаковы. Единственное преимущество - читаемость кода.nullptr
, что разница между 0 иNULL
Ответы:
В этом коде, кажется, нет преимущества. Но рассмотрим следующие перегруженные функции:
Какая функция будет вызвана? Конечно, намерение здесь заключается в том, чтобы позвонить
f(char const *)
, но на самом делеf(int)
будет называться! Это большая проблема 1 , не так ли?Итак, решение таких проблем заключается в использовании
nullptr
:Конечно, это не единственное преимущество
nullptr
. Вот еще один:Поскольку в шаблоне тип
nullptr
выводится какnullptr_t
, так что вы можете написать это:1. В C ++
NULL
определяется как#define NULL 0
, так оно и есть в основномint
, поэтому иf(int)
вызывается.источник
nullptr
? (Нет. Я не требую)NULL
Стандарт требует наличия целочисленного типа, поэтому он обычно определяется как0
или0L
. Также я не уверен, что мне нравится этаnullptr_t
перегрузка, так как она перехватывает только вызовыnullptr
, а не нулевой указатель другого типа, например(void*)0
. Но я могу поверить, что у него есть несколько применений, даже если все, что он делает, это сохраняет вас, определяя свой собственный однозначный тип заполнителя, чтобы он означал «нет».nullptr
имеет четко определенное числовое значение, тогда как константы нулевого указателя - нет. Константа нулевого указателя преобразуется в нулевой указатель этого типа (что бы это ни было). Требуется, чтобы два нулевых указателя одного типа сравнивались одинаково, а логическое преобразование превращает нулевой указатель вfalse
. Больше ничего не требуется. Следовательно, компилятор может (глупо, но возможно) использовать, например,0xabcdef1234
или другое число для нулевого указателя. С другой стороны,nullptr
требуется преобразовать в числовой ноль.f(nullptr)
не будет вызывать намеченную функцию? Было больше чем одна мотивация. Многие другие полезные вещи могут быть обнаружены самими программистами в ближайшие годы. Таким образом , вы не можете сказать , что есть только один истинный использование вnullptr
.C ++ 11 представляет
nullptr
, она известна какNull
константа указателя и повышает безопасность типов и разрешает неоднозначные ситуации в отличие от существующей константы нулевого указателя, зависящей от реализацииNULL
. Чтобы быть в состоянии понять преимуществаnullptr
. Сначала нам нужно понять, что естьNULL
и с чем связаны проблемы.Что
NULL
именно?Предварительно C ++ 11
NULL
использовался для представления указателя, который не имеет значения или указателя, который не указывает на что-либо допустимое. Вопреки распространенному мнению,NULL
это не ключевое слово в C ++ . Это идентификатор, определенный в заголовках стандартной библиотеки. Короче говоря, вы не можете использоватьNULL
без включения некоторых стандартных библиотечных заголовков. Рассмотрим пример программы :Вывод:
Стандарт C ++ определяет NULL как определенный макрос реализации, определенный в определенных заголовочных файлах стандартной библиотеки. Происхождение NULL происходит от C, а C ++ унаследовал его от C. Стандарт C определил NULL как
0
или(void *)0
. Но в C ++ есть небольшая разница.C ++ не может принять эту спецификацию как есть. В отличие от C, C ++ является языком со строгой типизацией (C не требует явного приведения
void*
к любому типу, в то время как C ++ требует явного приведения). Это делает определение NULL, заданное стандартом C, бесполезным во многих выражениях C ++. Например:Если NULL был определен как
(void *)0
, ни одно из приведенных выше выражений не будет работать.void *
кstd::string
.void *
требуется приведение от к указателю на функцию-член.Таким образом, в отличие от C, C ++ Standard обязывает определять NULL как числовой литерал
0
или0L
.Так зачем же нужна еще одна константа нулевого указателя, когда она у нас
NULL
уже есть ?Хотя комитет по стандартам C ++ разработал определение NULL, которое будет работать для C ++, у этого определения была своя собственная доля проблем. NULL работал достаточно хорошо почти для всех сценариев, но не для всех. Это дало неожиданные и ошибочные результаты для некоторых редких сценариев. Например :
Вывод:
Ясно, что намерение, похоже, состоит в том, чтобы вызвать версию, которая принимает
char*
в качестве аргумента, но так как вывод показывает, что вызывается функция, которая принимаетint
версию. Это потому, что NULL является числовым литералом.Кроме того, поскольку определяется реализацией, равен ли NULL 0 или 0L, может возникнуть путаница в разрешении перегрузки функции.
Пример программы:
Анализируя приведенный фрагмент:
doSomething(char *)
как и ожидалось.doSomething(int)
но, возможно,char*
версия была желательна, потому что0
IS также нулевой указатель.NULL
он определен как0
, вызовы,doSomething(int)
когда, возможно,doSomething(char *)
были предназначены, возможно, приводит к логической ошибке во время выполнения. ЕслиNULL
он определен как0L
, вызов является неоднозначным и приводит к ошибке компиляции.Таким образом, в зависимости от реализации один и тот же код может давать различные результаты, что явно нежелательно. Естественно, комитет по стандартам C ++ хотел исправить это, и это является основной мотивацией для nullptr.
Так что же
nullptr
и как избежать проблемNULL
?C ++ 11 вводит новое ключевое слово,
nullptr
чтобы служить константой нулевого указателя. В отличие от NULL, его поведение не определяется реализацией. Это не макрос, но у него есть свой тип. nullptr имеет типstd::nullptr_t
. C ++ 11 соответствующим образом определяет свойства для nullptr, чтобы избежать недостатков NULL. Подводя итог его свойств:Свойство 1: оно имеет свой собственный тип
std::nullptr_t
, асвойство 2: оно неявно конвертируемо и сопоставимо с любым типом указателя или указателем на член-тип, но
свойство 3: оно не является неявно конвертируемым или сопоставимым с целочисленными типами, за исключением
bool
.Рассмотрим следующий пример:
В вышеуказанной программе
char *
версия Calls , собственность 2 и 3Таким образом, введение nullptr позволяет избежать всех проблем старого доброго NULL.
Как и где вы должны использовать
nullptr
?Практическое правило для C ++ 11 просто начинайте использовать
nullptr
всякий раз, когда вы в противном случае использовали бы NULL.Стандартные ссылки:
Стандарт C ++ 11: C.3.2.4 Макрос NULL
Стандарт C ++ 11: 18.2 Типы
C ++ 11 Стандарт: 4.10 Преобразование указателей
Стандарт C99: 6.3.2.3 Указатели
источник
nullptr
, хотя я не знал, какое это имеет значение для моего кода. Спасибо за отличный ответ и особенно за усилия. Я пролил много света на эту тему.0xccccc....
, но переменная без значения является внутренним противоречием.bool flag = nullptr;
). Нет, не ОК, я получаю следующую ошибку во время компиляции с g ++ 6:error: converting to ‘bool’ from ‘std::nullptr_t’ requires direct-initialization [-fpermissive]
Настоящая мотивация здесь - идеальная пересылка .
Рассматривать:
Проще говоря, 0 - это специальное значение , но значения не могут распространяться через системные типы. Функции пересылки очень важны, и 0 не может справиться с ними. Таким образом, было абсолютно необходимо ввести
nullptr
, где тип является тем, что является особенным, и тип действительно может распространяться. Фактически, команда MSVC должна была представитьnullptr
досрочно после того, как они реализовали rvalue ссылки, а затем обнаружили эту ловушку для себя.Есть еще несколько угловых случаев, где
nullptr
можно облегчить жизнь, но это не основной случай, поскольку актеры могут решить эти проблемы. РассматриватьВызывает две отдельные перегрузки. Кроме того, рассмотрим
Это неоднозначно. Но, с nullptr, вы можете предоставить
источник
forward((int*)0)
работает. Я что-то упускаю?Основы nullptr
std::nullptr_t
является типом литерала нулевого указателя, nullptr. Это prvalue / rvalue типаstd::nullptr_t
. Существуют неявные преобразования из nullptr в нулевое значение указателя любого типа указателя.Литерал 0 - это целое число, а не указатель. Если C ++ обнаруживает, что смотрит на 0 в контексте, где может использоваться только указатель, он неохотно интерпретирует 0 как нулевой указатель, но это запасная позиция. Основная политика C ++ заключается в том, что 0 - это int, а не указатель.
Преимущество 1 - устранение неоднозначности при перегрузке указателя и целочисленных типов
В C ++ 98 основной причиной этого было то, что перегрузка указателей и целочисленных типов может привести к неожиданностям. Передача 0 или NULL таким перегрузкам никогда не вызывает перегрузку указателя:
Интересным в этом вызове является противоречие между кажущимся значением исходного кода («я называю fun с помощью NULL-нулевого указателя») и его фактическим значением («я вызываю fun с каким-то целым числом, а не нулевым»). указатель").
Преимущество nullptr в том, что он не имеет целочисленного типа. Вызов перегруженной функции fun с nullptr вызывает перегрузку void * (т. Е. Перегрузку указателя), потому что nullptr не может рассматриваться как что-то целое:
Использование nullptr вместо 0 или NULL позволяет избежать неожиданностей при перегрузке.
Еще одно преимущество по
nullptr
сравнениюNULL(0)
с использованием авто для типа возвратаНапример, предположим, что вы столкнулись с этим в кодовой базе:
Если вы случайно не знаете (или не можете легко выяснить), что возвращает findRecord, может быть неясно, является ли тип результата указателем или целым типом. В конце концов, 0 (какой результат проверяется) может пойти в любом случае. Если вы видите следующее, с другой стороны,
нет никакой двусмысленности: результат должен быть указателем типа.
Преимущество 3
Выше программа компилируется и выполняется успешно, но lockAndCallF1, lockAndCallF2 и lockAndCallF3 имеют избыточный код. Жаль писать такой код, если мы можем написать шаблон для всех этих
lockAndCallF1, lockAndCallF2 & lockAndCallF3
. Так что это можно обобщить с помощью шаблона. Я написал функцию шаблонаlockAndCall
вместо множественного определенияlockAndCallF1, lockAndCallF2 & lockAndCallF3
для избыточного кода.Код пересчитан, как показано ниже:
Подробный анализ , почему компиляция не удалось по
lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
неlockAndCall(f3, f3m, nullptr)
Почему сборка
lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
не удалась?Проблема в том, что когда 0 передается в lockAndCall, вычитается тип шаблона, чтобы выяснить его тип. Тип 0 - это int, так что это тип параметра ptr внутри экземпляра этого вызова lockAndCall. К сожалению, это означает, что при вызове func внутри lockAndCall передается int, что несовместимо с ожидаемым
std::shared_ptr<int>
параметромf1
. 0, переданный в вызове,lockAndCall
предназначался для представления нулевого указателя, но в действительности было передано int. Попытка передать это int в f1 какstd::shared_ptr<int>
ошибка типа. ВызовlockAndCall
с 0 завершается неудачно, потому что внутри шаблона int передается функции, которая требуетstd::shared_ptr<int>
.Анализ для вызова,
NULL
по сути, такой же. КогдаNULL
передаетсяlockAndCall
, целочисленный тип выводится для параметра ptr, и ошибка типа возникает, когдаptr
передается - тип int или int-likef2
, который ожидает получить astd::unique_ptr<int>
.В отличие от вызова с участием
nullptr
не имеет проблем. Когдаnullptr
передаетсяlockAndCall
, тип дляptr
выводится какstd::nullptr_t
. Когдаptr
передаетсяf3
, есть неявное преобразование изstd::nullptr_t
вint*
, потому чтоstd::nullptr_t
неявно преобразуется во все типы указателей.Рекомендуется, если вы хотите сослаться на пустой указатель, используйте nullptr, а не 0 или
NULL
.источник
Нет прямого преимущества
nullptr
в том, как вы показали примеры.Но рассмотрим ситуацию, когда у вас есть 2 функции с одинаковым именем; 1 дубль
int
и еще одинint*
Если вы хотите позвонить
foo(int*)
, передав NULL, то путь:nullptr
делает его более простым и интуитивно понятным :Дополнительная ссылка с веб-страницы Бьярне.
Не имеет значения, но на стороне C ++ 11 примечание:
источник
decltype(nullptr)
естьstd::nullptr_t
.typedef decltype(nullptr) nullptr_t;
. Я думаю, я могу посмотреть в стандарте. Ах, нашел его: Примечание: std :: nullptr_t - это отдельный тип, который не является ни типом указателя, ни указателем на тип члена; скорее, значение этого типа является константой нулевого указателя и может быть преобразовано в значение нулевого указателя или значение указателя нулевого элемента.nullptr
.Как уже говорили другие, его основное преимущество заключается в перегрузках. И хотя явные
int
перегрузки по сравнению с указателями могут быть редкими, рассмотрим стандартные библиотечные функции, такие какstd::fill
(которые укусили меня более одного раза в C ++ 03):Не компилировать:
Cannot convert int to MyClass*
.источник
IMO более важен, чем эти проблемы перегрузки: в глубоко вложенных шаблонных конструкциях трудно не потерять отслеживание типов, и предоставление явных подписей является довольно трудным делом. Таким образом, для всего, что вы используете, чем точнее сфокусировано на предполагаемой цели, тем лучше, это уменьшит необходимость явных подписей и позволит компилятору генерировать более проницательные сообщения об ошибках, когда что-то идет не так.
источник