item 8: 比起0和NULL更偏爱nullptr
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!
博客已经迁移到这里啦
先让我们看一些概念:字面上的0是一个int,不是一个指针。如果C++发现0在上下文中只能被用作指针,它会勉强把0解释为一个null指针,但这只是一个应变的方案。C++的主要规则还是把0视为int,而不是一个指针。
实际上,NULL也是这样的。NULL的情况,在细节方面有一些不确定性,因为C++标准允许它的实现不管是不是int只需要给出一个数值类型(比如,long)。虽然这和0不一样,但是事实上没有关系,因为这里的问题不是NULL的具体类型,而是0和NULL都不是指针类型。
在C++98中,这个问题造成的最主要的影响就是指针和数值类型的重载会导致意外情况。传入一个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类型的实现(编译器)拥有自由的权利。比如,如果NULL被定义为0L(也就是long类型的0),那么对函数的调用引起歧义,因为把long转换为int,bool,void*都是同样可行的。关于调用f的一个有趣的事情是源代码表面上的意义(我使用NULL---null指针,调用f)和它实际的意义(我使用某种整形---不是一个指针,调用f)之间的矛盾。这种反直觉的行为导致了C++98程序员指导方针让我们避免重载指针类型和整形类型。这个方针在C++11中仍然有效,因为,就算有这条Item的建议,尽管nullptr是更好的选择,有些开发者还是会继续使用0和NULL。
nullptr的优点不只是它不是一个整形类型。老实说,它也不是一个指针类型,但是你能把它想成是一个指向所有类型的指针。nullptr的实际类型是std::nullptr_t,在一个完美的循环定义(circular definition)中,std::nullptr_t被定义为nullptr的类型。std::nullptr_t类型能隐式转换到所有的原始(raw)指针类型,就是这个原因,让nullptr表现得像它是所有类型的指针。
用nullptr调用f的重载函数,实际会调用void*版本的函数(也就是指针版本的重载),因为nullptr不能被视为任何数值类型:
f(nullptr); //调用f(void*)版本的重载函数
因此使用nullptr来代替0和NULL避免了意外的重载解析,但是这不是他唯一的优点。它还能提升代码的可读性,尤其是在涉及auto变量时。举个例子,假设你遇到下面的代码:
auto result = findRecord(/* arguments*/);
if(result == 0){
...
}
如果你碰巧不知道(或者很难找出)findRecord返回的类型,这里对于result是指针类型还是整形类型就不明确了。总之,0(用来和result进行比较)可以是两个中的任意一个。但是,如果你看到下面的代码:
auto result = findRecord(/* arguments*/);
if(result == nullptr){
...
}
这里将没有歧义:result肯定是一个指针类型。
当template涉及后,nullptr将变得更加耀眼。假设你有一些函数,它们只能在适当的互斥量被锁住之后才能调用。每个函数使用不同类型的指针:
int f1(std::shared_ptr<Widget> spw);
double f2(std::unique_ptr<Widget> upw);
bool f3(Widget* pw);
想要传入null指针来调用函数的代码看起来像这样:
std::mutex f1m, f2m, f3m;
using MuxGuard = //C++11 的typedef,看Item 9
std::lock_guard<std::mutex>;
...
{
MuGuard g(f1m); //锁住f1的互斥量
auto result = f1(0); //传入0作为null指针给f1
} //解锁互斥量
...
{
MuGuard g(f2m); //锁住f2的互斥量
auto result = f2(NULL); //传入NULL作为null指针给f2
} //解锁互斥量
...
{
MuGuard g(f3m); //锁住f3的互斥量
auto result = f3(nullptr); //传入nullptr作为null指针给f3
} //解锁互斥量
没有用nullptr调用前面两个函数是令人忧伤的,但是代码能正常工作,这一点很重要。然而,代码中重复的模式(加锁,调用函数,解锁)不仅令人忧伤,这更是令人不安的。这种源代码的重复问题就是template被设计来解决的其中一种,所以让我们模板化这个模式:
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>
decltype(auto) lockAndCall(FuncType func, //C++14
MuxType& mutex,
PtrType ptr)
{
MuxGuard g(mutex);
return func(ptr);
}
给出lockAndCalltemplate(每个版本)的调用函数,能这么写:
auto result1 = lockAndCall(f1, f1m, 0); //错误
...
auto result2 = lockAndCall(f2, f2m, NULL); //错误
...
auto result3 = lockAndCall(f3, f3m, nullptr); //正确
很好,它们能这么写,但是,正如注释说的,前面两种情况,代码无法通过编译。第一种调用情况的问题是当0被传入lockAndCall时,template类型推导会找出它的类型。0的类型过去是,现在是,未来也永远是int,所以这就是在实例化的lockAndCall中,ptr参数的类型。不幸地,这意味着在lockAndCall中,调用func时,会传入一个int类型,然而它和f1需要的std::shared_patr参数不兼容。在lockAndCall调用中,尝试用传入0来代替一个null指针,但是实际上传入的是普通的int。尝试传入一个int作为std::shared_ptr给f1将产生类型错误。对于用0调用lockAndCall会失败,是因为在template中,一个int被传入一个需要std::shared_ptr的函数。(译注:理解起来很简单,这里没有int 到 指针类型的转换,只有0视为指针这一情况。所以调用会失败。)
对于调用涉及NULL的分析在本质上和0是一样的。当NULL被传入lockAndCall时,一个整形类型被推导为ptr参数的类型,并且当一个int或一个类int类型的参数被传入f2(需要一个std::unique_ptr类型的参数)时,类型错误就发生了。
作为对比,当调用涉及nullptr时没有问题。当nullptr被传入lockAndCall时,ptr的类型被推导为std::nullptr_t。当ptr被传入f3时,这里发生了一个从std::nullptr_t到Widget的隐式转换,因为std::nullptr_t*能隐式转换到任何类型的指针。
事实上,当你想使用一个null指针时,对于0和NULL,template类型推导推导出“错误”的类型
(也就是,它们的“对的"类型本应该是代表着一个null指针的),这是用nullptr代替0或NULL最重要的原因。使用nullptr,template不会面临特殊的挑战。结合nullptr不会遭受意外的重载解析(0和NULL很可能遭受)的事实,情况很明了了。当你想使用一个null指针的时候,使用nullptr,而不是0或NULL。
你要记住的事
比起0和NULL更偏爱nullptr
避免重载整形类型和指针类型。
item 8: 比起0和NULL更偏爱nullptr的更多相关文章
- item 10: 比起unscoped enum更偏爱scoped enum
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 一般情况下,在花括号中声明一个name(包括变量名,函数名),这个 ...
- Item 9: 比起typedef更偏爱别名声明(alias declaration)
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 我确信我们都同意使用STL容器是一个好主意,并且我希望在Item ...
- Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%。再往后,每提高0.1%,优化难度成指数级增长了。哪怕是千分之一,也直接影响用户体验,影响每天上万张机票的销售额。 在高并发场景下,提供了保证线程安全的对象、方法。比如经典的ConcurrentHashMap,它比起HashMap,有更小粒度的锁,并发读写性能更好。线程安全的StringBuilder取代S
Qunar机票技术部就有一个全年很关键的一个指标:搜索缓存命中率,当时已经做到了>99.7%.再往后,每提高0.1%,优化难度成指数级增长了.哪怕是千分之一,也直接影响用户体验,影响每天上万张机 ...
- 优先选择nullptr而不是0和NULL
我们知道:0是一个int,而不是一个指针.如果C++在一个只有指针才能够使用的上下文中发现它只有一个0,那么它会勉强将0解释成空指针,但那时一种倒退行为.C++的主要方针是0就是一个int,而不是指针 ...
- C++中 0 与 NULL 与 nullptr之间的关系,nullptr_t 的实现
C++中 0 与 NULL 与 nullptr之间的关系,nullptr_t 的实现 来源 http://blog.csdn.net/Virtual_Func/article/details/4975 ...
- mybatis sql语句中 in() 长度为0或null的情况
mybatis sql语句中 in() 长度为0或null的情况 比如: select * from A where colName IN <foreach collection="m ...
- Twitter Bootstrap 3.0 正式发布,更好地支持移动端开发
Twitter Bootstrap 3.0 终于正式发布了.这是一个圆滑的,直观的和强大的移动优先的前端框架,用于更快,更容易的 Web 开发.几乎一切都已经被重新设计和重建,更好的支持移动端设备. ...
- PHP中空字符串介绍0、null、empty和false之间的关系
PHP中空字符串介绍0.null.empty和false之间的关系 作者: 字体:[增加 减小] 类型:转载 时间:2012-09-25 用PHP开发那么久,PHP中空字符串.0.null.emp ...
- php中0,空,null和false之间区别
$a = 0; $b="0"; $c= ''; $d= null; $e = false; echo "5个变量-原始测试类型"; var_dump($a);/ ...
随机推荐
- 单元测试工具Junit浅谈
什么是单元测试? 写了一个类和一些方法,给别人用,会不会有bug?那就测一下这些方法吧 怎么测? 用main方法测?不能一起运行,需要人为观察输出是否正确,测试效率低 单元测试能带来什么好处? ...
- 09-OpenLDAP加密传输配置
OpenLDAP加密传输配置(CA服务器与openldap服务器异机) 阅读视图 环境准备 CA证书服务器搭建 OpenLDAP服务端与CA集成 OpenLDAP客户端配置 客户端测试验证 故障处理 ...
- AspNet Core2 浏览器缓存使用
Core2中使用Microsoft.AspNetCore.Mvc下的ResponseCacheAttribute特性来控制Http Get请求的缓存 原理是设置http请求 响应头的Cache-con ...
- Hp电脑开机报错:no boot disk has been detected or the disk has failed
hp主机开机报错no boot disk has been detected or the disk has failed,重启之后没有作用,开机之后仍然是同样界面.考虑是硬盘问题,按ESC+F10 ...
- Lua 与 C 交互之UserData(4)
lua作为脚本于要能够使用宿主语言的类型,不管是宿主基本的或者扩展的类型结构,所以Lua提供的UserData来满足扩展的需求.在Lua中使用宿主语言的类型至少要考虑到几个方面: 数据内存 生命周期 ...
- (转)Spring boot(一):入门篇
https://www.cnblogs.com/ityouknow/p/5662753.html#!comments 构建微服务:Spring boot 入门篇 什么是Spring Boot Spri ...
- 零基础掌握百度地图兴趣点获取POI爬虫(python语言爬取)(代码篇)
好,现在进入高阶代码篇. 目的: 爬取昆明市中学的兴趣点POI. 关键词:中学 已有ak:9s5GSYZsWbMaFU8Ps2V2VWvDlDlqGaaO 昆明市坐标范围: 左下角:24.390894 ...
- arcgis js api前端完成面积测算
想找一个不依赖GeometryService量算面积的方法,经过别人的帮助,终于在js帮助页上找到了.就是esri/geometry/geodesicUtils中的geodesicAreas方法,该方 ...
- [MySQL学习]STRICT_ALL_TABLES相应的OUT of RANGE VALUE FOR COLUMN和DATA truncated FOR COLUMN
版权声明:声明:本文档能够转载,须署名原作者. 作者:无为 qq:490073687 周祥兴 zhou.xiangxing210@163.com https://blog.csdn.net/Rooki ...
- 【转】BASE64编码简介
BASE64是一种编码方式,通常用于把二进制数据编码为可写的字符形式的数据. 这是一种可逆的编码方式. 编码后的数据是一个字符串,其中包含的字符为:A-Z.a-z.0-9.+./ 共64个字符:26 ...