条款8:优先选用 nullptr, 而非 NULL 或者0

ZaynPei Lv6

首先, 我们需要开门见山地指出, 0 和 NULL 都不具备指针类型, 它们的本质是整数,只是依赖于上下文(Context)被“特殊看待”为空指针

  • 0 的类型是 int:它是一个整数字面常量。C++ 只是在语言层面做一个不得已的“变通”:仅在上下文强制要求指针的语境中,才将 0 勉强解释为空指针。但在重载决议等情况下,它的“真实身份”——int——会优先被匹配。
  • NULL 的类型是整型:NULL 是一个预处理宏,它在 C++ 中通常被定义为 0(一个 int)或 0L(一个 long)。因此,NULL 也不是指针类型,它是一个整型字面量。

不区分三者最大的隐患:重载决议失败

假设有三个重载函数:

1
2
3
void f(int);    // 重载版本 #1
void f(bool); // 重载版本 #2
void f(void*); // 重载版本 #3 (接受指针)
分析 f(0) 和 f(NULL):

  • 调用 f(0):由于 0 的类型是 int,它与 f(int) 构成完美匹配。因此,编译器会毫不犹豫地选择调用重载版本 #1。它永远不会调用 f(void*)。
  • 调用 f(NULL):由于 NULL 通常被定义为 0(一个 int),这与调用 f(0) 的情况完全相同。编译器依然会选择 f(int)。

这完全违背了程序员的意图。程序员传入 0 或 NULL 的本意是想传递一个“空指针”,希望调用版本 #3,但结果却调用了 int 版本。

C++11 引入的 nullptr 解决了这个问题。nullptr 的类型不是整型。它的实际类型是 std::nullptr_t, 并且 std::nullptr_t 可以隐式转换为所有裸指针类型(如 void, int, Widget* 等)。

当调用 f(nullptr) 时,nullptr 无法被匹配为 f(int) 或 f(bool)。但它可以被隐式转换为 void*,因此它精确地匹配并调用了重载版本 #3,这完全符合程序员的意图。

提升代码清晰度:auto 与 nullptr

在现代 C++ 中,auto 的广泛使用使得 0 和 NULL 的歧义性更加突出。例如:

1
2
3
4
5
auto result = findRecord(/* ... */);

if (result == 0) { /* ... */ } // 这是什么意思?

if (result == nullptr) { /* ... */ } // 意思很明确
分析 result == 0:当读者看到这行代码时,无法立即判断 result 的类型。findRecord 返回的 result 究竟是一个指针类型,还是一个整型(例如,错误码或计数)?代码存在歧义。

分析 result == nullptr:这行代码则毫无歧义。它能通过编译的唯一前提是 result 必须是一个指针类型(或智能指针等支持与 nullptr 比较的类型)。这极大地提升了代码的清晰度。

保证模板类型推导的正确性

0 和 NULL 最大的失败在于模板类型推导。当它们被传递给模板时,编译器会推导它们的“真实”类型(即整型),而不是程序员“期望”的空指针。这种类型上的差异导致了在泛型代码中(如模板函数)传递空指针意图时,只有 nullptr 能够保证类型安全和编译成功。

???