文章目录
-
- [优先选用 nullptr,而非 0 或 NULL](#优先选用 nullptr,而非 0 或 NULL)
优先选用 nullptr,而非 0 或 NULL
优先选用 nullptr,而非 0 或 NULL,其核心原因可以总结为一句话:0和NULL的本质是整型,而nullptr 的本质才是真正的指针。**
在模板编程、函数重载以及类型推导中,把整型当指针用会引发各种让人抓狂的二义性和编译错误。
由于 0 是 int,NULL 在大多数编译器下被定义为 0 或者 0L(长整型),它们在重载决议中会被优先识别为数值,而不是指针。
cpp
#include <iostream>
// 重载函数 1:接收整型
void f(int x) {
std::cout << "调用了 f(int)\n";
}
// 重载函数 2:接收空指针
void f(void* p) {
std::cout << "调用了 f(void*)\n";
}
int main() {
// 1. 传入数值 0
f(0); // 输出:调用了 f(int)
// 原因:0 是字面量整型,完美匹配 f(int)
// 2. 传入宏 NULL
f(NULL); // 输出:调用了 f(int)
// 原因:NULL 实际上就是 0 或 0L,依然被当作整型处理
// 3. 用户的本意是想传空指针,但上面两个都调用错了!
// 4. 传入真正代表指针的 nullptr
f(nullptr); // 输出:调用了 f(void*)
// 原因:nullptr 的类型是 std::nullptr_t,它能隐式转换为任何指针类型,
// 但绝对不会转换为整型!
}
当模板进行自动类型推导时,0 和 NULL 的整型身份会彻底暴露,导致原本合法的代码直接编译报错。
cpp
//假设我们要写一个通用的"锁管理执行器"模板,它负责:上锁 -> 执行函数 -> 解锁。
#include <mutex>
#include <memory>
#include <utility>
std::mutex mtx;
// 模拟三个不同参数的底层函数
void g1(int* p) {}
void g2(std::shared_ptr<int> p) {}
void g3(int x) {}
// ==================== 泛型执行器模板 ====================
template<typename FuncType, typename PtrType>
void logAndExecute(FuncType func, PtrType param) {
std::lock_guard<std::mutex> lock(mtx); // 上锁
func(param); // 执行函数
}
int main() {
// ---- 场景 A:不使用模板,直接调用(由于隐式转换,强行能跑) ----
g1(0); // 勉强通过,0 可以隐式转换为 int*
g1(NULL); // 勉强通过
g1(nullptr); // 正确通过
// ---- 场景 B:使用模板(灾难降临) ----
// 1. 尝试传入 nullptr
logAndExecute(g1, nullptr); // 成功!
// 编译器推导:PtrType 为 std::nullptr_t。
// 在模板内部执行:g1(nullptr),std::nullptr_t 隐式转换为 int*,完美!
// 2. 尝试传入 0
// logAndExecute(g1, 0);
/*
【编译报错!】
编译器的内心活动:
1. 传入了数值 0,因此我推导 PtrType 的实际类型为 int。
2. 实例化模板内部代码:func(param) -> 变成了 g1(int x)。
3. 糟糕!g1 接收的参数是 int* 类型的指针,而你传给它一个 int 类型的变量(param)!
4. 整型变量是绝对不能隐式转换成指针的(字面量 0 可以,但装进变量里就不行了)。
5. 报错:无法将 'int' 转换为 'int*'
*/
// 3. 尝试传入 NULL
// logAndExecute(g2, NULL);
/*
【编译报错!】
同样的道理,NULL 被推导为 long 或 int。
在模板内部试图把一个整型变量传给 std::shared_ptr<int>,直接引发语法错误。
*/
}
除了编译期的绝对安全,nullptr 还能提升代码的清晰性。
cpp
// 假设你正在审查别人的代码,看到了这一行:
auto result = findRecord( /* 参数 */ );
//不清楚返回的是整数类型还是指针类型
if (result == 0) {
// ...
}
//返回的肯定是指针类型
if (result == nullptr) {
// ...
}