Item 8 优先使用nullptr
条款8:优先考虑nullptr而非0和NULL
很明显的一个问题:字面值 0 是一个 int 型的整数,不是一个指针
如果 C++ 发现当前上下文只能使用指针,他才会把 0 解释为指针,但那属于最后的退路,一般来说 C++ 的解析策略是把 0 看作 int 而不是一个指针
实际上 NULL 也是这样的。但是 NULL 的实现细节有些不确定因素,因为实现是被允许给NULL⼀个除了 int 之外的整型类型(⽐如 long)。这不常⻅,但也算不上问题所在。这⾥的问题不是NULL没有⼀个确定的类型,而是 0 和 NULL 都不是指针类型。
在 C++98 中,对指针类型和整型进⾏重载意味着可能导致奇怪的事情。如果给下⾯的重载函数传递 0 或 NULL,它们绝不会调⽤指针版本的重载函数:
1 | void f(int); //三个f的重载函数 |
而 f(NULL) 的不确定⾏为是由 NULL 的实现不同造成的。如果 NULL 被定义为 0L (指的是0为long类型),这个调⽤就具有⼆义性,因为从 long 到 int 的转换或从 long 到 bool 的转换或 0L 到 void* 的转换都会被考虑
有趣的是源代码表现出的意思(使⽤ NULL 调⽤ f)和实际想表达的意思(⽤整型数据调⽤ f)是相⽭盾的。这种违反直觉的⾏为导致 C++98 程序员都将避开同时重载指针和整型作为编程准则。在 C++11 中这个编程准则也有效
而 nullptr 的有点是它不是整型。其实严格来说也不算一个指针类型,但是可以把它认为是一个通用类型的指针。
nullptr 的类型是 std::nullptr_t ,带一个完美的循环定义后,std::nullptr_t ⼜被定义为 nullptr 。
std::nullptr_t 可以转换为指向任何内置类型的指针,这也是为什么我把它叫做通⽤类型的指针。使⽤ nullptr 调⽤f将会调⽤ void* 版本的重载函数,因为 nullptr 不能被视作任何整型:
使⽤nullptr*代替0和NULL可以避开了那些令⼈奇怪的函数重载决议,这不是它的唯⼀优势。它也可以使代码表意明确,尤其是当和auto⼀起使⽤时。举个例⼦,假如你在⼀个代码库中遇到了这样的代码:
1 | auto result = findRecord( /* arguments */ ); |
如果你不知道 findRecord 返回了什么,那么你就不太清楚到底 result 是⼀个指针类型还是⼀个整型。毕竟,0 也可以像我们之前讨论的那样被解析。但是换⼀种假设如果你看到这样的代码:
1 | auto result = findRecord( /* arguments */ ); |
这就没有任何歧义:result 的结果 ⼀定是指针类型。当模板出现时 nullptr 就更有⽤了。假如你有⼀些函数只能被合适的已锁互斥量调⽤。每个函数都有⼀个不同类型的指针:
1 | int f1(std::shared_ptr<Widget> spw); // 只能被合适的已锁互斥量调⽤ |
如果这样传递空指针:
1 | std::mutex f1m, f2m, f3m; // 互斥量f1m,f2m,f3m,各种⽤于f1,f2,f3函数 |
前两个调⽤没有使⽤ nullptr,但是代码可以正常运⾏,这也许对⼀些东西有⽤。但是重复的调⽤代码——为互斥量上锁,调⽤函数,解锁互斥量——更令⼈遗憾。它让⼈很烦。模板就是被设计于减少重复代码,所以让我们模板化这个调⽤流程:
1 | template<typename FuncType, typename MuxType, typename PtrType> |
可以写这样的代码调⽤lockAndCall模板:
1 | auto result1 = lockAndCall(f1, f1m, 0); // 错误! |
代码虽然可以这样写,但是就像注释中说的,前两个情况不能通过编译。在第⼀个调⽤中存在的问题是当 0 被传递给 lockAndCall 模板,模板类型推导会尝试去推导实参类型,0 的类型总是 int,所以 int 版本的实例化中的 func 会被 int 类型的实参调⽤。这与 f1 期待的参数 std::shared_ptr 不符。传递 0 本来想表⽰空指针,结果 f1 得到的是和它相差⼗万⼋千⾥的 int 。 把 int 类型看做 std::shared_ptr 类型⾃然是⼀个类型错误。在模板 lockAndCall 中使⽤ 0 之所以失败是因为得到的是 int 但实际上模板期待的是⼀个 std::shared_ptr。
第⼆个使⽤ NULL 调⽤的分析也是⼀样的。当 NULL 被传递给 lockAndCall,形参 ptr 被推导为整型,然后当 ptr ————⼀个 int 或者类似 int 的类型—————传递给 f2 的时候就会出现类型错误。当 ptr 被传递给 f3 的时候,隐式转换使 std::nullptr_t 转换为 Widget* ,因为 std::nullptr_t 可以隐式转换为任何指针类型。
模板类型推导将 0 和 NULL 推导为⼀个错误的类型,这就导致它们的替代品 nullptr 很吸引⼈。使⽤ nullptr,模板不会有什么特殊的转换。另外,使⽤ nullptr 不会让你受到同重载决议特殊对待 0 和 NULL ⼀样的待遇。当你想⽤⼀个空指针,使⽤ nullptr,不⽤ 0 或者 NULL。
总结:
- 优先考虑
nullptr而⾮ 0 和NULL - 避免重载指针和整型






