Item 8: Prefer nullptr to 0 and NULL

有一点需要明确:字面值0是int类型,并非指针类型。如果C++在只能使用指针的上下文中发现一个0,它会勉强的将0解释为空指针,但这是一个不得已而为之的行为。当然,C++的基本观点就是认为0是一个整形而不是指针。
实际上,NULL也是如此。因为标准允许不同的实现赋予NULL非int的整形(比如long),所以NULL在技术细节上有些不清不楚的成分。其实,这也不重要。关键在于0和NULL根本就不是指针类型。
在c++98中,这意味着指针和整数类型的重载可能会导致意外的结果。向下面这样的重载传递0或NULL从不会调用指针的重载:

void f(int); // three overloads of f
void f(bool);
void f(void*);

f(0); // calls f(int), not f(void*)

f(NULL); // might not compile, but typically calls
         // f(int). Never calls f(void*)

f(NULL)的不确定,就是因为NULL的类型上没有明确的规定。假设NULL被定义为0L,那f(NULL)的调用就有多义性了,因为从long到int,long到bool和从0L到void*的类型转换都被认为是一样好的。这就尴尬的很了,所以在C++98中最好避免指针和整形的重载。这个指导原则同样适用于C++11,因为C++11伟大的兼容。
nullptr的优势在于,它不具备整形类型。事实上,它也不具备指针类型,但是你可以把它想象为任意类型的指针。nullptr的实际类型是std::nullptr_t,并且在一个非常棒的循环定义下,std::nullptr_t被定义为nullptr的类型。std::nullptr_t可以隐式的转换到所有原始指针类型,这就使nullptr可以扮演成所有类型的指针。
所以,下面这个调用就很好,也没什么歧义。

f(nullptr); // calls f(void*) overload

除此之外,nullptr还可以提升代码的可读性,尤其是涉及到auto变量的应用。如下:

auto result = findRecord( /* arguments */ );

// 1. 
if (result == 0){
    // ...
}

// 2. 
if (result == nullptr){
    // ...
}

如果我们不知道或者不太容易知道findRecord的返回类型,那么上面代码的第2种写法就可以很清晰的指出result是否为指针了。第一种就比较模糊,有可能是整数0,也有可能是空指针。
nullptr在有模板存在的情况下,变现的更好。假设有如下代码,仅当适当的mutex被锁定时,才会执行一些函数。每个函数的形参是不同类型的指针:

int f1(std::shared_ptr<Widget> spw);     // call these only when
double f2(std::unique_ptr<Widget> upw);  // the appropriate
bool f3(Widget* pw);                     // mutex is locked

std::mutex f1m, f2m, f3m;                // mutexes for f1, f2, and f3
using MuxGuard = std::lock_guard<std::mutex>;

// ...

{
    MuxGuard g(f1m);         // lock mutex for f1
    auto result = f1(0);     // pass 0 as null ptr to f1
}                            // unlock mutex
// ...
{
    MuxGuard g(f2m);         // lock mutex for f2
    auto result = f2(NULL);  // pass NULL as null ptr to f2
}
// ...
// ...
{
    MuxGuard g(f3m);             // lock mutex for f3
    auto result = f3(nullptr);   // pass NULL as null ptr to f3
}

虽然前两个调用没有用nullptr,但是代码也能跑。当然,像上面这样写代码,有点冗余,我们用模板改造一下:

template<typename FuncType,
         typename MuxType,
         typename PtrType>
auto lockAndCall(FuncType func,
 MuxType& mutex,
 PtrType ptr) -> decltype(func(ptr))
{
    MuxGuard g(mutex);
    return func(ptr);
}

// C++14
template<typename FuncType,
         typename MuxType,
         typename PtrType>
decltype(auto) lockAndCall(FuncType func,
 MuxType& mutex,
 PtrType ptr)
{
    MuxGuard g(mutex);
    return func(ptr);
}

调用者可能会写出下面这样的代码:

auto result1 = lockAndCall(f1, f1m, 0);           // error!
// ...
auto result2 = lockAndCall(f2, f2m, NULL);        // error!
// ...

auto result3 = lockAndCall(f3, f3m, nullptr);     // fine

auto result1 = lockAndCall(f1, f1m, nullptr);     // also fine
auto result2 = lockAndCall(f2, f2m, nullptr);     // also fine

但是,前两种根本就编译不过。在第一个调用中,当0被传给lockAndCall时,模板类型推导启动并推算出它的类型。0的类型一直是int,所以ptr的类型被定为int,这意味着lockAndCall内部的func调用时,传入的时int,与f1所期望的std::shared_ptr参数不兼容。lockAndCall调用传入的0,不会像最前那样被解释成空指针。NULL的情况也是一样。相反,nullptr就不会有这个问题。当nullptr被传给lockAndCall时,ptr的类型被推导为std::nullptr_t。当ptr再传给f3时,会有一个std::nullptr_t到Widget*的隐式转换,因为std::nullptr_t可以被隐式转换到所有指针类型。
由于模板的类型推导和nullptr不代表任何整型,这两点就有效的支撑了论点:乖乖的用nullptr表示空指针是个好习惯!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值