优先选择nullptr而不是0和NULL

我们知道:0是一个int,而不是一个指针。如果C++在一个只有指针才能够使用的上下文中发现它只有一个0,那么它会勉强将0解释成空指针,但那时一种倒退行为。C++的主要方针是0就是一个int,而不是指针。

实际上来说,对于NULL也是一样。关于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(也就是说是long),那么该调用就很模棱两可,因为long到int的转换,long到bool的转换以及0L到void*的转换被认为一样好。关于该调用的一个有趣的事就是该源代码看起来的含义(”我用NULL(空指针)调用f”)和其实际的含义(”我用NULL(某种整数)调用f”)之间的冲突。这种违法直觉的行为促成了c++98程序员的一个指导方针就是避免重载指针类型和整型。该指导方针在C++11中依然有效,因为尽管我在这里建议大家用nullptr,但是很多开发者很可能还是继续使用0和NULL。

nullptr的优势在于它并不是一个整型。实话实说,它其实也不是一个指针类型,但是你可以认为它是所有类型的指针。nullptr的真实类型是std::nullptr_t,而std::nullptr_t又被定义成nullptr的类型,形成一个极好的环形定义。std::nullptr_t可以隐形转换任何原始指针类型,而正是这才让nullptr用起来像是所有类型的指针。

使用nullptr来调用重载函数f会调用void*版本的重载函数,因为nullptr不能被当做成一个整型来看:

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

所以使用nullptr而不是0或者NULL防止调用不合理的重载函数,但是这并不是其仅有的优势。它还可以提高代码的清晰度,尤其当你使用auto变量时。例如,假设你遇到下面这种代码:

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

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

如果你恰好不知道(或者不容易弄清楚)findRecord的返回类型,那么这个result是一个指针类型还是一个整型就可能不太清楚了。毕竟,0对于上面哪一种情况都适用。如果你看到下面的代码:

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

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

就没有任何歧义了:result就是一个指针类型

而当涉及到模板时,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,f3

using MuxGuard = std::lock_guard<std::mutex>; //C++11 typedef; See Item 9
...

{
    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
}                             //unlock mutex

{
    MuxGuard g(f3m);          //lock mutex for f3
    auto result = f3(nullptr);//pass nullptr as null ptr to f3
}                             //unlock mutex

该代码中前两个调用没有使用nullptr真是一个悲哀,但是这份代码可以正常工作,有一定价值。然而,调用代码中的重复模式—锁mutex,调用函数,解锁—却更加让人悲伤。这很令人烦。这种类型的代码重复是模板被设计用来避免的事物之一,所以咱们对这种模式使用模板:

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

如果你对于这种函数返回类型(auto…->decltype(func(ptr)))还不熟悉,那么请你参考Item 3,那里解释的很清楚。如果你使用C++14,返回类型还可以被精简成decltype(auto):

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

基于lockAndCall模板(哪一个版本都行),调用代码可以写成如下:

auto result1 = lockAndCall(f1,f1m,0);  //error!
...
auto result2 = lockAndCall(f2,f2m,NULL); //error!
...
auto result3 = lockAndCall(f3,f3m,nullptr); //fine

他们可以这样写,但是正如评论所说的,前两个并不会通过编译。第一个调用的问题在于0被传递给lockAndCall,模板类型推断计算出它的类型。0的类型,过去是,现在也一直都是int,所以在针对这个调用的lockAndCall实例中,其参数ptr的类型就是int。不幸的是,这意味着lockAndCall内部的func调用中,一个int被传递了,而这和f1期待的std::shared_ptr<Widget>参数类型不匹配。传递给lockAndCall的0本来打算是表示空指针的,但是传递进去的实际类型是一个普通的int。尝试给f1传递int当做其std::shared<Widget>参数会导致类型错误。使用0来调用lockAndCall会失败是因为在模板内部,一个int类型被传递给了一个需要std::shared_ptr<Widget>类型的函数里。

对于涉及到NULL的调用的分析和上面基本上一模一样。当NULL被传递给lockAndCall时,对参数ptr推断出的类型是一个整型,而当一个int或者类似于int的类型被传递个期待一个std::unique+ptr<Widget>f2时,就会发生类型错误。

作为对比,使用nullptr调用就不存在问题。当nullptr被传递给lockAndCall时,ptr的类型被推断成std::nullptr_t。当ptr被传递给f3时,有一个隐性转换将std::nullptr_t转换成Widget*类型,因为std::nullptr_t可以隐性转换成任意指针类型。

当你想要表示一个空指针时,使用nullptr而不是0或者NULL的最大一个原因就在于模板类型推断会给0和NULL推断出”错误的”类型(也就是说,它们的真实类型,而不是它们退化的含义,表示一个空指针)。使用nullptr,模板就不会出现什么问题。另外,nullptr不会导致像0和NULL那样的重载决议异常,所以,当你想要表示一个空指针时,使用nullptr,别使用0和NULL.

要点记忆

  • 优先选择nullptr而不是0和NULL
  • 避免对整型和指针类型进行重载
时间: 2024-10-28 16:30:38

优先选择nullptr而不是0和NULL的相关文章

Effective Java 第三版——47. 优先使用Collection而不是Stream来作为方法的返回类型

Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化. 在这里第一时间翻译成中文版.供大家学习分享之用. 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java

职场新人,优先选择在当前工作中提高自己

[来信] 贺老师: 您好! 早些年毕业的时候在你的blog上得到很多帮助,现在也是,我目前从事linux c开发,大部分工作完成一些嵌入式应用程序,但是我的硬件基础不好,往底层走感觉很吃力,最主要是我对调调驱动.移植系统不感兴趣. 目前薪水还过得去,但是出于自身兴趣和长远发展我觉得学习C++开发,我最近在看你的文章学C++,想要去做服务器后台开发或者cocos2dx游戏开发,但是很多朋友建议我转Java Android平台,因为他们觉得C++已经没落,正在逐渐被Java取代,所以很迷茫,希望老师

陈松松:制作视频优先选择这5种类型,总有一个适合你

这是我写的第88篇原创视频营销文章 陈松松,6年视频营销实战经验 万事开头难! 就看谁先能挺住! 很多朋友发现制作视频也有很多类型,不知道选择哪种最适合自己,今天我就跟大家详细分享下,当你学习之后,你根据自己的情况去选择和深入学习,也相当于给了你一个参考方向: 第一种:套视频模板 难度系数:三星 只需要修改文字.替换掉自己的图片,一般这种模板类的视频在10秒-2分钟左右,只要你的视频模板多,都可以制作出非常多的视频. 虽然套视频模板的视频非常简单,任何没有基础的人都可以操作,你用哪种视频软件的模

PHP中空字符串介绍0、null、empty和false之间的关系

0是数字,是empty,是false,不是null,值相当于空字符串,但类型不是字符串,去空格或强制转换为字符串型时不等于空字符串 ""的值相当于0,是empty,是空字符串,是false,不是null,不是数字字符串 null的值相当于空字符串,值相当于0,是empty,是false,不是字符串,不是数字0 "=="只要值相等就满足条件: "==="需要两个变量的值和类型都相等: strval();将变量转换为字符串类型: intval();将

php中0,空,null和false之间区别

$a = 0; $b="0"; $c= ''; $d= null; $e = false; echo "5个变量-原始测试类型"; var_dump($a);//int 0 var_dump($b);//string '0' var_dump($c);//string '' var_dump($d);//null var_dump($e);//boolean false echo "<h4>empty测试</h4>"; v

js 中 0 和 null 、&quot;&quot; 的逻辑关系

在做字符串非空判断时,无意发现一个问题,记录下以便以后回顾. 问题描述:非空判断,只是校验传值的内容是否为"".null .undefined.当变量 赋值的字符串内容为 0,此时做非空校验竟然返回true,说当前变量值为空. 代码如下: 1 //模拟赋值 0 2 var str = "0"; 3 4 /** 5 * 判断字符是否为空的方法 6 * 为空 返回 true 7 * 不为空返回 false 8 * @param {Object} obj 9 */ 10

mybatis sql语句中 like in() 长度为0或null的情况

mybatis sql语句中 like in() 长度为0或null的情况 比如: select * from A where colName LIKE IN <foreach collection="moCodeList" item="item" index="index" open="(" close=")" separator=","> #{item} </for

赋值为0或null,和不赋值的区别

目录 赋值为0或null,和不赋值的区别 第一部分 第二部分 成员变量要是对象类型 静态变量是在什么时候赋值的 赋值为0或null,和不赋值的区别 第一部分 如果在方法中声明变量,声明时不赋值和给个null是不一样的,赋值null也相当于完成了初始化赋值,这个时候可以通过编译也可以可以调用对象方法,但必然会报空指针异常.但Java中成员变量不需要赋予初始值,但一般会有一个默认初值,基本类型如int初值为0,除基本类型变量外,其他对象的初始值都是null. 总之,一个局部对象使用之前肯定要先赋值,

启动APP遇到“UiAutomator exited unexpectedly with code 0, signal null”解决

今天我在调试APP自动化的时候遇到了一个问题,如今解决了总结下: 首先,我的前置工作都准备的很好了,然后就想着运行下我的代码,可是天不遂人愿,遇到了这么一个问题: 看上图报错:UiAutomator exited unexpectedly with code 0, signal null 我的解决问题步骤是这样的: 1.adb shell 命令进入shell模式 2.进入到data/local/tmp 目录下面 3.用ls命令查看当前的目录文件: 4.将AppiumBootstrap.j 这个文