我们知道: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(); // 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 == ){
...
}

如果你恰好不知道(或者不容易弄清楚)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(); //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,);  //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
  • 避免对整型和指针类型进行重载

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

  1. item 8: 比起0和NULL更偏爱nullptr

    本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 先让我们看一些概念:字面上的0是一个int,不是一个指针.如果C+ ...

  2. C++中 0 与 NULL 与 nullptr之间的关系,nullptr_t 的实现

    C++中 0 与 NULL 与 nullptr之间的关系,nullptr_t 的实现 来源 http://blog.csdn.net/Virtual_Func/article/details/4975 ...

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

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. 企业开发中选择logback而不是log4j的理由

    不知道看到这篇文章的Java工程师有没有考虑过这个问题:为什么在企业开发中会选择logback来记录日志,而不是log4j呢? 如果你以前没有考虑过这个问题,那么现在如果让你考虑一下,你可能觉的会是因 ...

  5. Effective C# 学习笔记(原则二:为你的常量选择readonly而不是const)

    原则二.为你的常量选择readonly而不是const      Prefer readonly to const 对于常量,C#里面有两个不同的版本:运行时常量(readonly)和编译时常量(co ...

  6. 由于想要实现下载的文件可以进行选择,而不是通过<a>标签写死下载文件的参数,所以一直想要使用JFinal结合ajax实现文件下载,但是ajax实现的文件下载并不能触发浏览器的下载文件弹出框,这里通过模拟表单提交实现同样的效果。

    由于想要实现下载的文件可以进行选择,而不是通过<a>标签写死下载文件的参数,所以一直想要使用JFinal结合ajax实现文件下载(这样的话ajax可以传递不同的参数),但是ajax实现的文 ...

  7. 选择 FreeBSD 而不是 Linux 的技术性原因3

    选择 FreeBSD 而不是 Linux 的技术性原因3 jail FreeBSD Jails 系统是另一个惊人的工程壮举. 在 2000 年 3 月 14 日的 4.0 版本中,FreeBSD 引入 ...

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

    PHP中空字符串介绍0.null.empty和false之间的关系 作者: 字体:[增加 减小] 类型:转载 时间:2012-09-25   用PHP开发那么久,PHP中空字符串.0.null.emp ...

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

    $a = 0; $b="0"; $c= ''; $d= null; $e = false; echo "5个变量-原始测试类型"; var_dump($a);/ ...

随机推荐

  1. [sklearn] 实现随即梯度下降(SGD)&分类器评价参数查看

    直接贴代码吧: 1 # -*- coding:UTF-8 -*- 2 from sklearn import datasets 3 from sklearn.cross_validation impo ...

  2. 引入第三方SDK allowBackup value不一致引起的编译异常

    项目中要引入一个客服的SDK,项目中 <application android:name=".AppApplication" android:allowBackup=&quo ...

  3. Class的isAssignableFrom方法

    Class类的isAssignableFrom是个不常用的方法,感觉这个方法的名字取得不是很好,所以有必要在此解析一下,以免在看源码时产生歧义,这个方法的签名如下: public native boo ...

  4. ini文件解析c库(iniparser)【转】

    转自:http://www.cnblogs.com/dyllove98/archive/2013/07/28/3221732.html 一.交叉编译ini解析库 .官方网站http://ndevill ...

  5. Kafka监控KafkaOffsetMonitor【转】

    1.概述 前面给大家介绍了Kafka的背景以及一些应用场景,并附带上演示了Kafka的简单示例.然后,在开发的过程当中,我们会发现一些问题,那就是消息的监控情况.虽然,在启动Kafka的相关服务后,我 ...

  6. python3+requests库框架设计04-配置文件

    python3配置文件的增删改查等操作可以使用内置的ConfigParser模块,可以自行百度学习,也可以看Python3学习笔记27-ConfigParser模块 配置文件一般存放着环境信息,比如u ...

  7. 左侧滚动条js

    <script> var left = document.getElementById('main-left'); var right = document.getElementById( ...

  8. powerdesigner 使用技巧 建模工具 导出sql 导出实体类 导出word

    显示comment列 Table Properties(表属性)=>Columns(列)=>Customize Columns and Filter(自定义列过滤) 勾上 comment ...

  9. windows 中java开发环境搭建

    安装jdk1.6 1.下载tomcat服务器 2. 解压缩tomcat  * 不要使用含有空格和中文目录 3.启动tomcat之前 配置 JAVA_HOME 环境变量 ------ 配置JDK安装路径 ...

  10. struct/class等内存字节对齐问题详解

    问题引入 定义一个结构体的一般形式为: struct 结构体名 { //类型说明符 成员名; }; 例如有如下结构体: struct Stu { int id; char sex; float hig ...