nullptr 的基础知识
std::nullptr_t 是空指针字面量 nullptr 的类型。它是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 调用 fun——空指针”)和它的实际含义(“我用某种整数调用 fun——不是空指针”)。
nullptr 的优点是它没有整数类型。
用 nullptr 调用重载函数 fun 调用 void* 重载(即指针重载),因为 nullptr 不能被视为任何整数:
fun(nullptr); // calls fun(void*) overload
使用 nullptr 代替 0 或 NULL 从而避免重载决议意外。
使用 auto 作为返回类型时,nullptr 相对于 NULL(0) 的另一个优势
例如,假设您在代码库中遇到这种情况:
auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}
如果你碰巧不知道(或不能轻易找出) findRecord 返回的内容,可能不清楚 result 是指针类型还是整数类型。毕竟,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,所以这就是这个 lockAndCall 调用的实例化中的参数 ptr 的类型。不幸的是,这意味着在 lockAndCall 内部对 func 的调用中,传递了一个 int,这与 f1 所期望的 std::shared_ptr<int> 参数不兼容。在对lockAndCall 的调用中传递的 0 旨在表示一个空指针,但实际上传递的是 int。试图将此 int 作为 std::shared_ptr<int> 传递给 f1 是类型错误。使用 0 调用 lockAndCall 失败,因为在模板内部,一个 int 被传递给需要 std::shared_ptr<int> 的函数。
对涉及NULL的调用的分析基本相同。当NULL 传递给lockAndCall 时,会为参数ptr 推导出一个整数类型,当ptr(一个int 或类似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。