-
本Item内容较简单,养成良好习惯多用
nullptr
少用0
和NULL
就好了。实际上,我认为可以说前者是从语言层面填补了C++98时代没有一个明确的空指针类型的空缺,因此在条件允许的情况下使用前者是完全优于后者的。以下是作者给出的几个更详细的原因。 -
0
的本质是int
,只有当不得已时才会被转化为指针。NULL
同理(注:NULL
是一个宏定义,查看源码MSVC和GCC根据平台将其定义为了0
或0LL
。作为参考,其在C语言中的定义一般为(void *)0
)。这里的重点是二者都不具有指针类型,这在函数重载中尤其会导致问题:
更有意思的情况是,如果
NULL
被定义为long
,那么会调用哪个重载版本?转换成int
,bool
和void*
看起来是差不多的同等好的选择。
- 因此在C++98中,一般建议是不要给函数同时重载整型和指针型参数。
nullptr
的优势在于其没有一个整型类型。说实话它也不是指针类型,但你可以认为它是一个可以指向任何类型的指针。nullptr
的真实类型是std::nullptr_t
,通过一个循环定义,std::nullptr_t
的类型又被定义为nullptr
。std::nullptr_t
可以被隐式转换为任意裸指针类型。- 笔者注:MSVC将
std::nullptr_t
定义在<stddef.h>
中,GCC则在<c++config.h>
中,定义方式均为typedef decltype(nullptr) nullptr_t
。至于反过来的定义,由于nullptr
是一个C++关键字,猜测应该不会出现在C++标准库实现的代码中,而是由编译器处理。也因为这种循环定义关系,当悬浮查看std::nullptr_t
的类型时,会看到比较诡异的结果。
-
使用
nullptr
调用以上函数不会引起歧义,调用的是void *
参数版本。
-
使用
nullptr
的另一好处是增强代码的清晰性,例如:
// 调用某个我们不了解细节的函数
auto result = findRecord( /* arguments */ );
if (result == 0) { // result是整型还是指针?
...
}
if (result == nullptr) { // 很明确, result是指针
...
}
- 第三个好处与模板参数有关。作者举的原例子代码略长,但道理不复杂,这里进行一定简化:
template<typename FuncType, typename PtrType>
decltype(auto) callFuncOnPtr(FuncType func, PtrType ptr) {
return func(ptr);
}
int f(void*) { return 0; }
int main()
{
int a = callFuncOnPtr(f, 0); // 编译错误
int b = callFuncOnPtr(f, NULL); // 同上
int c = callFuncOnPtr(f, nullptr); // ok
return 0;
}
- 如果我们想经过模板函数
callFuncOnPtr
调用参数为空指针的func
,很遗憾0
和NULL
都行不通,因为在模板推导时ptr
就会被认为是int
型,而func
需要的参数为void *
,导致编译错误(其实笔者这里没有完全理解:ptr
不能在func
调用时再经过一次隐式转换吗?因为直接调用f(0)
是没有问题的。或许对于模板只有一次定类型的机会?希望有高手能解答:)该场景下,使用nullptr
是完全没有问题的。
总结
- 倾向于使用 nullptr 而不是 0 或 NULL。
- 避免同时重载整型和指针型函数。