Каковы преимущества использования nullptr?

Этот fragment кода концептуально делает то же самое для трех указателей (инициализация безопасного указателя):

int* p1 = nullptr; int* p2 = NULL; int* p3 = 0; 

Итак, каковы преимущества назначения указателей nullptr над назначением им значений NULL или 0 ?

    7 Solutions collect form web for “Каковы преимущества использования nullptr?”

    В этом коде не кажется, что это преимущество. Но рассмотрим следующие перегруженные функции:

     void f(char const *ptr); void f(int v); f(NULL); //which function will be called? 

    Какую функцию вызывать? Конечно, намерение здесь состоит в том, чтобы называть f(char const *) , но в действительности мы будем называть f(int) ! Это большая проблема 1 , не так ли?

    Таким образом, решение таких проблем – использование nullptr :

     f(nullptr); //first function is called 

    Конечно, это не единственное преимущество nullptr . Вот еще один:

     template struct something{}; //primary template template<> struct something{}; //partial specialization for nullptr 

    Поскольку в шаблоне тип nullptr выводится как nullptr_t , вы можете написать это:

     template void f(T *ptr); //function to handle non-nullptr argument void f(nullptr_t); //an overload to handle nullptr argument!!! 

    1. В C ++ NULL определяется как #define NULL 0 , поэтому он в основном является int , поэтому вызывается f(int) .

    C ++ 11 вводит nullptr , он известен как константа Null pointer и улучшает безопасность типов и устраняет неоднозначные ситуации, в отличие от существующей константы NULL зависящей от реализации. Чтобы понять преимущества nullptr . мы сначала должны понять, что такое NULL и какие проблемы связаны с ним.


    Что такое NULL точно?

    Pre C ++ 11 NULL использовался для представления указателя, который не имеет значения или указателя, который не указывает на что-либо действительное. Вопреки популярному понятию NULL не является ключевым словом в C ++ . Это идентификатор, определенный в стандартных заголовках библиотек. Короче говоря, вы не можете использовать NULL не включая некоторые стандартные заголовки библиотек. Рассмотрим пример программы :

     int main() { int *ptr = NULL; return 0; } 

    Вывод:

     prog.cpp: In function 'int main()': prog.cpp:3:16: error: 'NULL' was not declared in this scope 

    Стандарт C ++ определяет NULL как макрос, определенный реализацией, определенный в некоторых стандартных файлах заголовков библиотеки. Происхождение NULL от C и C ++ унаследовало его от C. Стандарт C определял NULL как 0 или (void *)0 . Но в C ++ есть тонкая разница.

    C ++ не мог принять эту спецификацию так, как она есть. В отличие от C, C ++ является строго типизированным языком (C не требует явного приведения от void* к любому типу, тогда как C ++ требует явного перевода). Это делает определение NULL, заданное стандартом C бесполезным во многих выражениях C ++. Например:

     std::string * str = NULL; //Case 1 void (A::*ptrFunc) () = &A::doSomething; if (ptrFunc == NULL) {} //Case 2 

    Если значение NULL определено как (void *)0 , то ни одно из вышеприведенных выражений не будет работать.

    • Случай 1: не будет компилироваться, потому что требуется автоматический приведение из void * в std::string .
    • Случай 2: не будет компилироваться, потому что требуется отбрасывать из void * в указатель на функцию-член.

    Таким образом, в отличие от стандарта C, C ++, для определения NULL в качестве числового литерала 0 или 0L .


    Итак, в чем же необходимость в еще одном нулевом указателе, когда у нас уже есть NULL ?

    Хотя в комитете по стандартам С ++ появилось определение NULL, которое будет работать на C ++, это определение имело свою собственную долю проблем. NULL работал достаточно хорошо для почти всех сценариев, но не для всех. Это дало неожиданные и ошибочные результаты для некоторых редких сценариев. Например :

     #include void doSomething(int) { std::cout< <"In Int version"; } void doSomething(char *) { std::cout<<"In char* version"; } int main() { doSomething(NULL); return 0; } 

    Вывод:

     In Int version 

    Очевидно, что намерение состоит в том, чтобы вызвать версию, которая принимает char* в качестве аргумента, но поскольку на выходе отображается функция, которая вызывает вызов int версии. Это потому, что NULL является числовым литералом.

    Кроме того, поскольку определяется реализацией, является ли NULL 0 или 0L, может быть много путаницы в разрешении перегрузки функции.

    Пример программы:

     #include  void doSomething(int); void doSomething(char *); int main() { doSomething(static_cast (0)); // Case 1 doSomething(0); // Case 2 doSomething(NULL) // Case 3 } 

    Анализ вышеприведенного fragmentа:

    • Случай 1: вызовы doSomething(char *) как ожидалось.
    • Случай 2: вызовы doSomething(int) но, возможно, была doSomething(int) char* потому что 0 IS также является нулевым указателем.
    • Случай 3: Если 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 .

    Рассмотрим следующий пример:

     #include void doSomething(int) { std::cout< <"In Int version"; } void doSomething(char *) { std::cout<<"In char* version"; } int main() { char *pc = nullptr; // Case 1 int i = nullptr; // Case 2 bool flag = nullptr; // Case 3 doSomething(nullptr); // Case 4 return 0; } 

    В вышеуказанной программе,

    • Случай 1: ОК - Свойство 2
    • Случай 2: Не Ok - Свойство 3
    • Случай 3: ОК - Свойство 3
    • Случай 4: нет путаницы - Calls char * version, Property 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 Указатели

    Настоящая мотивация здесь – идеальное направление .

    Рассматривать:

     void f(int* p); template void forward(T&& t) { f(std::forward(t)); } int main() { forward(0); // FAIL } 

    Проще говоря, 0 является специальным значением , но значения не могут распространяться через системные типы. Функции пересылки необходимы, и 0 не может справиться с ними. Таким образом, было абсолютно необходимо ввести nullptr , где тип является тем, что является особенным, и тип действительно может распространяться. На самом деле команде MSVC пришлось вводить nullptr досрочно после того, как они внедрили ссылки rvalue, а затем обнаружили эту ловушку для себя.

    Есть еще несколько случаев, когда nullptr может облегчить жизнь, но это не основной случай, так как приведение может решить эти проблемы. Рассматривать

     void f(int); void f(int*); int main() { f(0); f(nullptr); } 

    Вызывает две отдельные перегрузки. Кроме того, рассмотрим

     void f(int*); void f(long*); int main() { f(0); } 

    Это неоднозначно. Но с помощью nullptr вы можете предоставить

     void f(std::nullptr_t) int main() { f(nullptr); } 

    Основы nullptr

    std::nullptr_t – тип литерала с нулевым указателем, nullptr. Это prvalue / rvalue типа std::nullptr_t . Существуют неявные преобразования от значения nullptr к нулевому указателю любого типа указателя.

    Литерал 0 – это int, а не указатель. Если C ++ обнаруживает, что ищет 0 в контексте, где может использоваться только указатель, он неохотно интерпретирует 0 как нулевой указатель, но это резервная позиция. Основной политикой C ++ является то, что 0 является int, а не указателем.

    Преимущество 1 – Устранение двусмысленности при перегрузке указателей и интегральных типов

    В C ++ 98 основной причиной этого было то, что перегрузка по указателю и интегральным типам может привести к неожиданностям. Передача 0 или NULL для таких перегрузок никогда не вызывала перегрузку указателя:

      void fun(int); // two overloads of fun void fun(void*); fun(0); // calls f(int), not fun(void*) fun(NULL); // might not compile, but typically calls fun(int). Never calls fun(void*) 

    Интересная вещь в этом вызове – это противоречие между очевидным значением исходного кода («Я вызываю удовольствие с NULL – нулевой указатель») и его фактическим значением («Я вызываю удовольствие с каким-то целым числом, а не с нулевым указатель”).

    Преимущество nullptr заключается в том, что он не имеет интегрального типа. Вызов перегруженной функции fun с помощью nullptr вызывает перегрузку void * (т. Е. Перегрузку указателя), поскольку nullptr нельзя рассматривать как что-либо интегральное:

     fun(nullptr); // calls fun(void*) overload 

    Использование nullptr вместо 0 или NULL позволяет избежать сюрпризов с перегрузкой.

    Еще одно преимущество nullptr над NULL(0) при использовании auto для возвращаемого типа

    Например, предположим, что вы встретите это в базе кода:

     auto result = findRecord( /* arguments */ ); if (result == 0) { .... } 

    Если вы не знаете (или не можете легко узнать), что возвращает findRecord, может быть неясно, является ли результат типом указателя или интегральным типом. В конце концов, 0 (какой результат тестируется) может идти в любом случае. Если вы видите следующее, с другой стороны,

     auto result = findRecord( /* arguments */ ); if (result == nullptr) { ... } 

    нет никакой двусмысленности: результатом должен быть тип указателя.

    Преимущество 3

     #include #include  #include  #include  using namespace std; int f1(std::shared_ptr spw) // call these only when { //do something return 0; } double f2(std::unique_ptr upw) // the appropriate { //do something return 0.0; } bool f3(int* pw) // mutex is locked { return 0; } std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3 using MuxtexGuard = std::lock_guard; void lockAndCallF1() { MuxtexGuard g(f1m); // lock mutex for f1 auto result = f1(static_cast(0)); // pass 0 as null ptr to f1 cout< < result<(NULL)); // pass NULL as null ptr to f2 cout< < result< 

    Выше программы компилируются и выполняются успешно, но lockAndCallF1, lockAndCallF2 и lockAndCallF3 имеют избыточный код. Жаль написать такой код, если мы можем написать шаблон для всех этих lockAndCallF1, lockAndCallF2 & lockAndCallF3 . Поэтому его можно обобщить с помощью шаблона. Я написал функцию шаблона lockAndCall вместо множественного определения lockAndCallF1, lockAndCallF2 & lockAndCallF3 для избыточного кода.

    Код обновляется следующим образом:

     #include #include  #include  #include  using namespace std; int f1(std::shared_ptr spw) // call these only when { //do something return 0; } double f2(std::unique_ptr upw) // the appropriate { //do something return 0.0; } bool f3(int* pw) // mutex is locked { return 0; } std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3 using MuxtexGuard = std::lock_guard; template auto lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) -> decltype(func(ptr)) //decltype(auto) lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) { MuxtexGuard g(mutex); return func(ptr); } int main() { auto result1 = lockAndCall(f1, f1m, 0); //compilation failed //do something auto result2 = lockAndCall(f2, f2m, NULL); //compilation failed //do something auto result3 = lockAndCall(f3, f3m, nullptr); //do something return 0; } 

    Детальный анализ, почему компиляция не удалась для 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 который ожидает f1 . Значение 0, переданное в вызове lockAndCall предназначалось для представления нулевого указателя, но то, что на самом деле было передано, было int. Попытка передать этот int в f1, поскольку std::shared_ptr - это ошибка типа. Вызов lockAndCall с 0 не выполняется, потому что внутри шаблона int передается функции, для которой требуется std::shared_ptr .

    Анализ для вызова с использованием NULL в основном одинаков. Когда NULL передается в lockAndCall , выводится интегральный тип для параметра ptr, и возникает ошибка типа, когда ptr -an int или int-like type-передается в f2 , который ожидает получить std::unique_ptr ,

    Напротив, вызов с использованием nullptr вызывает проблем. Когда nullptr передается lockAndCall , тип для ptr выводится как std::nullptr_t . Когда ptr передается в f3 , происходит неявное преобразование из std::nullptr_t в int* , потому что std::nullptr_t неявно преобразуется во все типы указателей.

    Рекомендуется, всякий раз, когда вы хотите ссылаться на нулевой указатель, используйте nullptr, а не 0 или NULL .

    Нет прямого преимущества наличия nullptr в том, как вы показали примеры.
    Но рассмотрите ситуацию, когда у вас есть 2 функции с одинаковым именем; 1 принимает int а другой – int*

     void foo(int); void foo(int*); 

    Если вы хотите вызвать foo(int*) , передав NULL, тогда путь:

     foo((int*)0); // note: foo(NULL) means foo(0) 

    nullptr делает его более легким и интуитивным :

     foo(nullptr); 

    Дополнительная ссылка с веб-страницы Бьярне.
    Неприемлемо, но на C ++ 11 Примечание:

     auto p = 0; // makes auto as int auto p = nullptr; // makes auto as decltype(nullptr) 

    Как уже говорили другие, его основное преимущество заключается в перегрузках. И в то время как явные int и vinter могут быть редкими, рассмотрите стандартные библиотечные функции, такие как std::fill (которые укусили меня более одного раза в C ++ 03):

     MyClass *arr[4]; std::fill_n(arr, 4, NULL); 

    Не компилируется: Cannot convert int to MyClass* .

    ИМО важнее, чем проблемы с перегрузкой: во глубоко вложенных конструкциях шаблонов трудно не потерять информацию о типах, а давать явные подписи – это довольно эффективная работа. Таким образом, для всего, что вы используете, чем точнее сосредоточено на намеченной цели, тем лучше, это уменьшит необходимость явных подписей и позволит компилятору создавать более проницательные сообщения об ошибках, когда что-то пойдет не так.

    Interesting Posts

    Почему включение аппаратного ускорения в CSS3 замедляет производительность?

    Улучшила ли Microsoft Windows Scandisk, CHKDSK и Defrag в Windows 7?

    Windows 10 Free Upgrade – Что происходит с Windows 7/8?

    Один монитор работает точно так же, как двойной монитор?

    Следующие фильтры не могли выбрать их форматы: Parsed_amerge_5 Рассмотрите возможность вставки фильтра формата (a) возле их ввода или вывода в FFmpeg

    Excel CSV – формат ячейки

    Где хранятся файлы физического шрифта?

    Java ищет метод с конкретной аннотацией и элементом annotations

    Как показать загрузчик в jQuery?

    В Chrome отключено подключение IPv6, таймауты в Firefox

    Автоматическое обновление Flash Player при запуске Windows

    Вызов Console.WriteLine из нескольких streamов

    Предварительный просмотр камеры для Android

    Поддержка JPA для Java 8 новых API даты и времени

    Как нажимать или нажимать текст TextView на разные слова?

    Давайте будем гением компьютера.