nullptr 解决函数重载的二义性
面试官: 在 C++11 之前,我们通常使用一个宏 NULL 来表示空指针。而在 C++11 中,引入了一个新的关键字 nullptr 来专门表示空指针。
请问,我们为什么需要 nullptr?或者说,使用宏 NULL 在某些情况下,会带来什么样的问题或歧义,是 nullptr 能够解决的?
回答:
NULL是一个宏,它除了表示空指针之外,更常用的含义是表示0,一个整数。- 由于
NULL既能表示空指针,又能表示0,因此它会引发歧义。 - 比如,某个函数重载了两个版本,一个的参数类型是
int,而另一个参数类型是char*。当输入NULL时,通常会调用参数为int的那个版本。 - 而使用
nullptr则规避了这一风险。nullptr是一个真正的空指针常量,它是一个特殊的类型std::nullptr_t,可以隐式地转换为任何的指针类型,而不会和int类型混淆。
面试官 (总结与肯定): 你说的完全正确。我们来把这个过程再详细地梳理一遍。nullptr 诞生的核心原因是解决函数重载时的二义性。
NULL 的“前世今生”
NULL 是一个定义在 <cstddef> 等头文件中的预处理宏。它的具体定义取决于编译器,但通常是下面两种之一:
| |
在 C++98/03 的时代,你可以认为 NULL 就是一个值为 0 的整型常量。
问题所在:函数重载 正如你所举的例子,当我们有这样的函数重载时:
| |
func(NULL)被调用。- 预处理器将
NULL直接文本替换为0。代码变成了func(0);。 - 编译器现在需要为
func(0)选择一个最佳的重载版本。 0是一个整型字面量 (integer literal)。- 在 C++ 的重载决议规则中,
int版本的匹配优先级,要高于“整型0转换为空指针”的匹配优先级。 - 因此,编译器选择了
func(int)。
输出:Called func(int)
这完全违背了程序员“我想传递一个空指针”的意图。
nullptr 如何解决问题
nullptr是 C++11 引入的一个关键字,它不是宏。- 它有一个特殊的类型,叫做
std::nullptr_t。 - 这个
std::nullptr_t类型,可以隐式地转换为任何指针类型(int*,char*,TreeNode*…),但它不能隐式地转换为任何整型(如int,long)。
所以,如果我们调用 func(nullptr);:
- 编译器看到
nullptr。 - 它尝试匹配
func(int)-> 失败!nullptr不能转成int。 - 它尝试匹配
func(char*)-> 成功!nullptr可以转成char*。 - 最终,编译器毫无歧义地选择了
func(char*)。
黄金法则:
在现代 C++ 代码中,永远使用
nullptr,绝不使用NULL。
你对这个核心区别的理解已经非常到位了。
类型推导中的 nullptr 和 NULL
面试官 (深入提问): 好的,我们知道了 nullptr 在函数重载时的巨大优势。
我们再来看一个与模板类型推导相关的问题。auto 和模板函数在推导类型时,会如何处理 NULL 和 nullptr?
请看下面这段代码:
| |
(提示:typeid(...).name() 的输出结果是编译器相关的,不完全标准,但足以区分类型。比如 int 可能是 i,long 可能是 l,std::nullptr_t 可能是 Dn。)
回答:
- 我认为,
p1的类型被推导为int,p2的类型被推导为std::nullptr_t。 - 输出的两行分别是
i和Dn。
面试官 (解释与确认):
你的判断非常精准。这个例子进一步揭示了 NULL 和 nullptr 在类型系统中的本质区别。
1. auto p1 = NULL;
NULL在大多数实现中,就是一个值为0的宏。auto在推导0这个整型字面量时,会根据0的具体类型来决定。0本身是int类型。- 因此,
p1的类型被推导为int。 print_type(NULL)也就是print_type(0),模板参数T被推导为int。输出结果在不同编译器上可能是i(GCC/Clang) 或int(MSVC)。
2. auto p2 = nullptr;
nullptr的类型是std::nullptr_t。- 因此,
p2的类型被推导为std::nullptr_t。 print_type(nullptr),模板参数T被推导为std::nullptr_t。输出结果可能是Dn(GCC/Clang) 或std::nullptr_t(MSVC)。
这个区别为什么重要?
因为它再次表明,NULL 在 C++ 的类型系统中,其本质就是一个整数。如果你把它用在泛型代码(模板、auto)中,它就会暴露出自己“整数”的真面目,导致意想不到的类型推导错误。
而 nullptr 则始终保持其“空指针常量”的纯粹身份,无论是在重载决议还是模板推导中,都能保证正确的、符合指针语义的行为。
你对 nullptr 的理解已经非常深入了。
nullptr 是纯右值
面试官 (最终问题): 好的,最后一个小问题,作为一个知识点的补充。
nullptr 既然有自己的类型 std::nullptr_t,那么它是一个左值还是右值?
| |
回答:
- 回顾左值和右值的区别:左值可以取地址,而右值不行。
nullptr是不能取地址的,它只是一个作为空指针而存在的临时量,所以,nullptr是右值。
面试官 (总结与肯定):
你说得对极了。nullptr 是一个纯右值 (prvalue)。
因此,auto ptr = &nullptr; 这行代码是无法编译通过的,因为我们不能对一个右值取地址。
这进一步加强了 nullptr 作为一种特殊字面量 (literal) 的概念,它和 10, true, 'a' 这些字面量一样,都是存在于代码中的值,而不是存储在特定内存位置的具名对象。
总结
今天我们系统地辨析了 nullptr 和 NULL 的区别,这也是现代 C++ 程序员必须掌握的基础知识。
- 我们理解了
NULL作为宏,在函数重载和类型推导中存在的二义性问题。 - 我们学习了
nullptr作为带类型的关键字,如何通过更严格的类型规则来解决这些问题。 - 我们还确认了
nullptr是一个右值。
你今天的表现非常出色,能够快速理解概念,并进行准确的逻辑推理。继续保持!