Nullptr为什么是安全的


前言

c++11特性引出了nullptr来代替null和0。因此,我们不仅疑惑为什么要代替呢。null和0本身有什么不足缺点吗。为此,特此用这篇博客总结下


一、nullptr和NULL具体实现

在 C 语言中,我们使用 NULL 表示空指针,它实际上是一个宏,具体被定义为:

/* C 语言程序 */
#define NULL ((void*)0)
#define NULL 0 /* 两者皆可 */

因为在C 语言中,是允许 void 指针隐式转换为其它类型指针的,所以 #define NULL ((void*)0) 这样的定义不会有问题。

C++ 语言出现后,为了保持对 C 语言的兼容,保留了 NULL,但对 NULL 的定义变得更为严格:

/* C++ 语言程序 */
#ifdef __cplusplus
    #define NULL 0
#else
    #define NULL ((void*)0)
#endif

NULL 被定义为 0,而不是 ((void*)0),因为在 C++ 语言中,void 指针是不可以隐式转换为其它类型指针的,必须显示转换,

/* C++ 语言程序 */
#define NULL ((void*)0) /* 如果在 C++ 语言中这么定义的话 */

int* a = NULL; /* 隐式转换,错误 */
int* a = (int*)NULL; /* 显示转换,正确,但很麻烦,所以 NULL 都会被定义为 0 */

在 C++98 之前(包括 C++98),在对 NULL 的使用上,都一直存在一个问题,假设有下面的代码,

/* C++ 语言程序 */
void func(int i);
void func(char* p);

func(NULL); /* 该调用哪个?*/

NULL 其实就是等于 0,对于上面的两个函数,它都是符合的,如此,就会出现语义二义性的错误

为了解决上述重载函数所带来的问题,C++11 的 nullptr 应运而生。nullptr 实质上是一个常量,实现代码大致如下,

/* C++ 语言程序 */
const /* 常量 */
class
{
public:
    template<class T>
    operator T*() const /* 向任意类型的非成员指针转换 */
    {
        return 0;
    }

    template<class C, class T>
    operator T C::*() const /* 向任意类型的成员指针转换 */
    {
        return 0;
    }

private:
    void operator&() const /* 不可取地址 */
    {
    }
} nullptr = {};

nullptr 只是一个常量,这就意味着我们可以在程序中随意定义一个与其名称相同的标识符,但因为 nullptr 在实际编程中的应用实在太广泛,因此 C++ 编译器一般都会把 nullptr 定为关键字,避免程序员的滥用。

了解了nullptr和NULL的具体实现,接下来我们再讲讲如下内容

二、为什么优先选用nullptr,而非0或NULL

c++98中,NULL可能在指针性别和整型之间进行重载时发生意外。
如果向这样的重载函数传递0和NULL,是从来不会调用到要求指针型别的重载版本的。

void f(int); 	//f的三个重载版本
void f(bool);
void f(void*);

f(0); //调用的是f(int),而不是f(void*)
f(NULL); //可能通不过编译,但一般会调用f(int)。从来不会调用f(void*)

f(NULL)的不确定性是NULL的型别在实现中的余地的一种反映。
对于C++98的程序员来说,指导原则是不要在指针型别和整型之间做重载。

nullptr的优点在于,它不具备整型型别。实话实说,它也不具备指针型别,但可以把它想成一种任意型别的指针。nullptr的实际型别是std::nullptr_t,并且,在一个漂亮的循环定一下,std::nullptr_t的定义被指定为nullptr的型别。型别std::nullptr_t可以隐式转换到所有的裸指针型别,这就是为何nullptr可以扮演所有型别指针的原因

调用重载函数f时传入nullptr会调用void*那个重载版本(带指针形参的重载版本),因为nullptr无法视作任何一种整型:

f(nullptr); //调用的是f(void*)这个重载版本

因此,使用nullptr而非0和NULL就避免了重载决议中的意外,但这不是它仅有的优点。它还可以提升代码的清晰性,尤其在涉及auto变量时。
如下代码:

auto result=findRecord(/*实参*/);
if(result==0)
{
}

如果刚好不知道(或不容易得出)findRecord的返回值类别的话,那么result是指针型别还是整数型别就不清楚了。 毕竟0(作为findRecord的检测值)两者都有可能。
但是如下代码:

auto result=findRecord(/*实参*/);
if(result==nullptr)
{
}

这会就没有多义性了:result必然具备指针型别

模板型别推导会将0和NULL推导成“错误”型别(即它们的真实型别,而非退而求其次的表示空指针这个意义)。而使用nullptr,模板就不会带来特殊的麻烦。
并且nullptr也不会造成重载决议的问题

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值