Основи нульптр
std::nullptr_t
- тип нульового вказівника, буквальний, nullptr. Це первісне / rvalue типу std::nullptr_t
. Існують неявні перетворення від nullptr до null pointer значення будь-якого типу вказівника.
Буквальний 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 полягає в тому, що він не має інтегрального типу. Виклик перевантаженої функції весело за допомогою nullptr викликає void * overload (тобто перевантаження вказівника), тому що nullptr не може розглядатися як щось цілісне:
fun(nullptr); // calls fun(void*) overload
Використання nullptr замість 0 або NULL, таким чином, дозволяє уникнути сюрпризів у роздільній здатності перевантаження.
Ще одна перевага nullptr
над NULL(0)
використанням авто для типу повернення
Наприклад, припустимо, ви стикаєтеся з цим у кодовій базі:
auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}
Якщо ви не знаєте (або не можете легко дізнатися) про те, що findRecord повертається, можливо, не буде зрозуміло, чи є тип вказівника чи інтегральний тип. Зрештою, 0 (на який результат тестується) може піти в будь-який бік. Якщо ви бачите таке, з іншого боку,
auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}
немає двозначності: результат повинен бути типом вказівника.
Перевага 3
#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
//do something
return 0;
}
double f2(std::unique_ptr<int> 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<std::mutex>;
void lockAndCallF1()
{
MuxtexGuard g(f1m); // lock mutex for f1
auto result = f1(static_cast<int>(0)); // pass 0 as null ptr to f1
cout<< result<<endl;
}
void lockAndCallF2()
{
MuxtexGuard g(f2m); // lock mutex for f2
auto result = f2(static_cast<int>(NULL)); // pass NULL as null ptr to f2
cout<< result<<endl;
}
void lockAndCallF3()
{
MuxtexGuard g(f3m); // lock mutex for f2
auto result = f3(nullptr);// pass nullptr as null ptr to f3
cout<< result<<endl;
} // unlock mutex
int main()
{
lockAndCallF1();
lockAndCallF2();
lockAndCallF3();
return 0;
}
Вище компіляція програми та успішно виконується, але lockAndCallF1, lockAndCallF2 та lockAndCallF3 мають надлишковий код. Шкода писати такий код, якщо ми можемо написати шаблон для всіх цих lockAndCallF1, lockAndCallF2 & lockAndCallF3
. Тож це може бути узагальнено за допомогою шаблону. У мене написана функція шаблону lockAndCall
замість кратного визначення lockAndCallF1, lockAndCallF2 & lockAndCallF3
надмірного коду.
Код повторно враховується нижче:
#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
//do something
return 0;
}
double f2(std::unique_ptr<int> 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<std::mutex>;
template<typename FuncType, typename MuxType, typename PtrType>
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. На жаль, це означає, що у виклику функціонувати всередині lockAndCall передається int, який не сумісний з очікуваним std::shared_ptr<int>
параметром f1
. 0, переданий у виклику до, повинен lockAndCall
був представляти нульовий покажчик, але те, що насправді було передано, було int. Спроба передати цей int до f1 як a std::shared_ptr<int>
- це помилка типу. Виклик lockAndCall
з 0 не вдається, оскільки всередині шаблону передається int функції, яка вимагає а std::shared_ptr<int>
.
Аналіз участі у виклику NULL
по суті однаковий. Після NULL
передачі до lockAndCall
параметра ptr виводиться інтегральний тип, і помилка типу виникає, коли ptr
—ant або int-подібний тип — передається до f2
, який очікує отримання а std::unique_ptr<int>
.
Навпаки, дзвінок із залученням nullptr
не має проблем. Коли nullptr
передається lockAndCall
, ptr
виводиться тип для std::nullptr_t
. Коли ptr
передається f3
, відбувається неявна конверсія з std::nullptr_t
в int*
, оскільки std::nullptr_t
неявно перетворюється на всі типи вказівників.
Рекомендується, коли ви хочете звернутися до нульового вказівника, використовуйте nullptr, а не 0 або NULL
.
int
таvoid *
не вибиратимеint
версію надvoid *
версією при використанніnullptr
.