C++11新特性——nullptr

转自https://blog.csdn.net/u011304970/article/details/72229013

C/C++中的NULL

  • C++98中NULL是个宏定义,明确规定NULL是个整型0值:
/* Define NULL pointer value */
#ifndef NULL
    #ifdef __cplusplus
        #define NULL    0
    #else  /* __cplusplus */
        #define NULL    ((void *)0)
    #endif  /* __cplusplus */
#endif  /* NULL */
  • C中NULL规定为void*指针:
#define  NULL  ((void*)0)
  • 可以看出,C和C++中NULL的规定是不同的,原因在于C++的重载机制
void foo(int) {}
void foo(int*) {}

foo(0);  // 调用foo(int)而不是foo(int*)
foo(NULL);  // 如果NULL是0,则调用foo(int); 如果NULL是0L,则编译错误
  • foo(0)调用的是foo(int),因为0是int值,与foo(int)严格匹配,当NULL为0时,foo(NULL)也是同样的情况。如果NULL为0L时,则编译错误,因为没有一个函数是严格匹配,0L又同时可以转型为int和int*,编译器无法决定调用哪个函数。因此在C++98中,尽可能不以整型和指针重载函数是一个良好的习惯。

nullptr的引入

  • 为什么要引入nullptr呢?C++98中的0即表示整数字面值0,又表示指针的0值:
class Foo
{
public: 
    void bar() {}
};

int *p = 0;  // 0表示普通指针的0值
void (Foo::*pBar)() = 0;  // 0表示指向成员函数的指针的0值
std::shared_ptr<int> pi = 0;  // 0表示智能指针的0值

int x = 0;  // 0表示整数0
  • 就算是用NULL表示空指针,也是换汤不换药,C++98规定NULL是一个整型0值,根据不同的编译器实现,可能是0或0L。更尴尬的是,可以拿NULL当合法的整型(int或者long)来使用:
int *q = NULL; // 看起来不错,NULL表示指针的0值
int y = NULL;  // 尴尬
long z = NULL * NULL;  // 尴尬
  • 另外NULL是个宏定义,宏的缺点很多,C++并不建议使用宏,而建议使用const变量代替。关键的问题在于C++98没有一种方法可以区别整数0值和指针0值。硬生生的让0一人分饰两角。在重载整型和指针的场合就会遇到麻烦。同样以刚开始的例子为例:
void foo(int) {}
void foo(int*) {}

foo(0);  // 调用foo(int)而不是foo(int*)
foo(NULL);  // 如果NULL是0,则调用foo(int); 如果NULL是0L,则编译错误
  • 我们调用foo(NULL)的意图是调用foo(int*),却无论如何也做不到。这就造成了表象(用指针调用)和实质(用整数调用)的不一致,让人混乱且费解。如果需要调用foo(int*),必须将0显示转换成指针:
foo(static_cast<int*>(NULL));  // 调用foo(int*)
  • 这样做麻烦至极。如果能有一个指针字面值表示空指针,就可以避免这样的转换。我们可以自己定义一个类,以实现这个目的(以下代码出自Scott Meyers教授的《Effective C++》第二版):
const // It is a const object...
class nullptr_t 
{
  public:
    template<class T>
    inline operator T*() const // convertible to any type of null non-member pointer...
    { return 0; }

    template<class C, class T>
    inline operator T C::*() const   // or any type of null member pointer...
    { return 0; }

  private:
    void operator&() const;  // Can't take address of nullptr

} nullptr = {};
  • 关键的地方在于这个类可以通过operator T*和operator T C::*转换成任何类型的指针,却不能转换成int。引入了这个类和这个类的一个实例nullptr之后,就可以区分整型0值和指针0值了:
foo(0);
foo(nullptr);
  • 这样做很好,但仍需做一些额外的工作,而且C++语言本身没有表示指针0值的东西,这不免有些缺憾。于是,为了卸下0的重担,为了区别对待整数0值和指针0值,为了填补缺憾, C++11引入了nullptr。

nullptr

  • C++引入了新的关键字nullptr,表示指针的0值,虽然它不是指针类型,但是它能够转换成任何指针,而且它不能转换成int:
class Foo
{
public:
    void bar() {}
};

int *p = nullptr;  // nullptr表示普通指针的0值
void (Foo::*pBar)() = nullptr;  // nullptr表示指向成员函数的指针的0值
std::shared_ptr<int> pi = nullptr;  // nullptr表示智能指针的0值

int x = 0;  // 0表示整数0
//int y = nullptr; // 错误!不能将nullptr转换为int值
  • 这样就将整数0值和指针0值区分开了。也可以解决整型和指针重载的问题:
void foo(int) {}
void foo(int*) {}

foo(0);  // 调用foo(int)
foo(nulptr);  // 调用foo(int*)
  • 虽然nullptr的引入可以解决整型和指针重载函数的问题,但是C++11没有消除0的空指针性质,因此该问题依然存在,只是在nullptr的掩护下被隐藏了而已。因此尽可能不以整型和指针重载函数仍是一个良好的习惯。

std::nullptr_t

  • nullptr也有其自身的类型,它的类型为std::nullptr_t。std::nullptr_t本身不是指针类型,但是它可以转换为任何指针类型,这也就是nullptr可以看做任何指针类型的原因。它的定义如下:
typedef decltype(nullptr) nullptr_t;
  • 上面是一个很有趣的类型定义。通常情况是先有类型,然后才有该类型的变量。而上面的定义则是,先有一个某类型的字面值,再由这个字面值定义它的类型。妙哉!

  • 可以定义以nullptr_t类型作为形参的函数,该函数只能接指针0值:

void f(std::nullptr_t) {}

f(0);
f(nullptr);
f(NULL);

int *p = nullptr;
//f(p);  // 错误,p不是指针0值

int x = 0;
//f(x); // 错误,x不是指针0值

判断是否是std::nullptr_t

  • c++14提供了一个类模板来判断一个类型是否是std::nullptr_t:
template< class T >
struct is_null_pointer;
  • 该模板提供了一个bool类型的类静态常量value,表示T是否为std::nullptr_t。并且重载了operator bool和operator()用来返回value的值。
assert(std::is_null_pointer<std::nullptr_t>::value == true);
assert(std::is_null_pointer<const std::nullptr_t>::value == true);
assert(std::is_null_pointer<volatile std::nullptr_t>::value == true);
assert(std::is_null_pointer<const volatile std::nullptr_t>::value == true);
assert(std::is_null_pointer<int *>::value == false);

auto nilp = std::is_null_pointer<std::nullptr_t>();
assert(nilp == true);
assert(nilp() == true);
  • c++17定义了一个常量值is_null_pointer_v以简化操作:
template< class T >
inline constexpr bool is_null_pointer_v = is_null_pointer<T>::value;
  • 用法:
assert(std::is_null_pointer_v<std::nullptr_t> == true);

总结

  • C规定NULL是void*指针,C++规定NULL是整型0值(宏定义)。

  • 在需要空指针的场合使用nullptr代替0和NULL。

  • nullptr不是指针类型,但可以转换为任何指针类型。

  • 不以整型和指针重载函数是一个良好的习惯。

  • nullptr的类型是std::nullptr_t。

  • c++11与C++14引入了is_null_pointer和is_null_pointer_v判断一个类型是否是std::nullptr_t。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值