C++11 中,nullptr 是空指针,可给指向任意对象类型的指针赋值
整型 (integral types) = bool, char, short, int, long, long long and their unsigned counterparts
1 调用重载函数
C++ 中,0 首先被视为 int 型,而 NULL 首先被视为整型 (integral type),至于具体是 int、long 或是其它类型,根据情况而定。
下面三个函数,形参类型不同,构成重载函数。如果各自传递三个不同的实参,来选择调用哪个函数,则会出现如下问题:
// three overloads of f
void f(int);
void f(bool);
void f(void*);
f(0); // calls f(int), not f(void*)
f(NULL); // might not compile, but typically calls f(int). Never calls f(void*)
f(nullptr); // calls f(void*) overload
问题1: 0 首先为 int 型,因此,调用 f(0) 即调用 f(int)
问题2: NULL 复杂些,如果 NULL 被定义为普通的 0,则调用 f(int);如果 NULL 被定义成 0L,则 long -> int, long -> bool, 0L -> void*, 三种情况都是合法的,此时,编译器会报错
使用 nullptr,则不会有重载函数调用模糊的问题
- nullptr 不属于整型,也不是普通意义上的指针;
- nullptr 实际类型为 std::nullptr_t,它能够隐式的转换成所有的原始指针类型,可视为一个能够指向所有类型的指针
2 代码清晰
使用 nullptr 代替 0 或 NULL,能显著提高代码的可读性,尤其和 auto 连用时,如下:
auto result = findRecord( /* arguments */ );
if (result == 0) {
...
}
auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}
当 0 与 result 比较时,不确定 findRecord 的返回值类型 (可能是整型,也可能是指针类型)。当 nullptr 和 result 比较时,可知 findRecord 的返回值,必定是一个指针类型。
3 模板函数
当程序中有模板 (template) 时,nullptr 的好处更加明显:
// call these only when the appropriate mutex is locked
int f1(std::shared_ptr<Widget> spw);
double f2(std::unique_ptr<Widget> upw);
bool f3(Widget* pw);
// calling code that wants to pass null pointers
std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxGuard = std::lock_guard<std::mutex>;
...
{
MuxGuard g(f1m); // lock mutex for f1
auto result = f1(0); // pass 0 as null ptr to f1
} // unlock mutex
...
{
MuxGuard g(f2m); // lock mutex for f2
auto result = f2(NULL); // pass NULL as null ptr to f2
} // unlock mutex
...
{
MuxGuard g(f3m); // lock mutex for f3
auto result = f3(nullptr); // pass nullptr as null ptr to f3
} // unlock mutex
lock mutex -> call function -> unlock mutex,这个模式在程序中重复了三次,要想避免这种重复,可用一个模板函数代替
template<typename FuncType, typename MuxType, typename PtrType>
auto lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) -> decltype(func(ptr)) // C++11
{
MuxGuard g(mutex);
return func(ptr);
}
最后,调用该模板函数
auto result1 = lockAndCall(f1, f1m, 0); // error!
...
auto result2 = lockAndCall(f2, f2m, NULL); // error!
...
auto result3 = lockAndCall(f3, f3m, nullptr); // fine
当 0 作为实参传递给 lockAndCall 函数时,其被 C++ 编译器推断为 int 型,这与 f1 所期望的 std::shared_ptr 型参数明显不符,因此出现报错。
同理,NULL 与 f2 期望的 std::unique_ptr 型参数也不符合。
而 nullptr,作为 ptr 传给 f1 或 f2 时,被推断为 std::nullptr_t;作为 ptr 传给 f3 时,std::nullptr_t 会隐式的转换成 Widget*,确保了参数类型的一致。
小结
- prefer nullptr to 0 and NULL
- avoid overloading on integral and pointer types
参考资料
《Effective Modern C++》 item 8
《C++ Programming Language》 "integral types"