[译]GotW #1: Variable Initialization
原文地址:http://herbsutter.com/2013/05/09/gotw-1-solution/
第一个问题强调的是要明白自己在写什么的重要性。下面有几行简单的代码--它们大多数之间都有区别,尽管在语法上只有轻微的不一样。
Problem
JG Question
1. 下面代码存在差异吗?
widget w; // ( a ) widget w(); // ( b )
widget w{}; // ( c ) widget w( x ); // ( d )
widget w{ x }; // ( e ) widget w = x; // ( f )
widget w = { x }; // ( g ) auto w = x; // ( h )
auto w = widget { x }; // ( i )
Guru Questions
2. 下面每行代码都做了什么?
vevtor<int> v1 (, ); // ( a )
vector<int> v2 { , }; // ( b )
3. 除了上面代码,使用{}初始化对象还有什么其他好处?
4. 在什么时候应该使用()或者{ }语法来初始化对象?为什么?
Solution
上面的代码说明了一下几件事情:
· 默认初始化、直接初始化、拷贝初始化和列表初始化之间的区别。
· 使用()和{ }初始化的区别。
· A red herring that isn’t initialization at all( 译注:※),现代C++应彻底避免。
最重要的是:如果坚持在#4节介绍的两个原则,就可以忽略上面的大多数情况,规则很简单,却可以在默认的情况下提供高效的性能。
Answer 1.
(a)是默认初始化
widget w; // ( a )
上面代码声明了一个类型为widget,名字叫w的变量,对于大多数类型,它们是使用默认的构造函数widget::widget()来初始化的。
要注意的是,如果widget是内建类型,比如int或者一些类似int(int-like)的
1.依赖编译器为它们产生默认的构造函数,
2.且没有虚函数或者
3.虚基类或者
4数据成员初始化函数
的类型或者满足类似满足上述条件的类型,那么w是没有被初始化的,且包含了一些“垃圾”值。
(b)是令人烦恼的red herring(转移注意力的话题),现在主要属于一个历史性的玩意。
widget w(); // ( b )
这是一个前-现代C++的陷阱:大致一看,它好像是一个使用默认构造函数widget::widget()的变量声明语句,但是这里存在一个个语法的二义性。它可以是一个名字为w的,不带参的,返回值为widget的(传值)的函数声明。
免得可以能会认为:“这里的圆括号()是多余的,这是程序员自己犯的错,他可以写成widget w;这样”。但是要注意的是存在着一个偶然的情况下会引起同样的问题,比如可能会用临时变量来初始化:
// same problem ( gadget and dooead are types ) widget w( gadget( ), doodad( ) ); // pitfall: not a variable declaration
Scott Meyers老早前就把它命名为“C++’s most vexing parse(C++最棘手的解析)”。因为C++标准在面对这样的二义性时,是以“如果它可以是一个函数声明,那么它就是函数声明”为标准的。
好消息是,这只是属于历史的一个玩意,在现代C++代码中应该是不会遇到的,因为C++11移除了这陷阱。注意C++11并没有改变代码的意思,C++11对C++98有着很强的向后兼容性,同样包括这样的二义性。C++11是通过提供一个语法,几乎在所有情况下来取代(b)这种情况,因此没必要再次掉入这样的陷阱当中了,如:
(c)是非棘手且明确的
widget w{ }; // ( c )
我们有了第一个优先选择{}的理由:对于任意类型widget,上面代码做了(a)和(b)应该做的事--总是初始化变量,且它不会存在函数声明的二义性。没有烦恼,没有混乱。
“啊,等等,没有它看上去那么简单!”有人可能会反对。“假如widget有一个带std::initializer_list参数的构造函数怎么办?因此如果有的话,就不能这样调用的吧?”
答案是否定的,它就是看上去那么简单。因为标准明确:一个空的{}列表意味着调用默认的构造函数,如果有的话。但是,应该注意有std::initializer_list这样的情况,接着看下一个。
(d)和(e)都是直接初始化
现在考虑从已存在的变量来初始化w的情况:
widget w(x); // (d)
widget w{x}; // (e)
假设x不是一个类型名,上面两个都是直接初始化。因为变量w通过调用widget::widget(x)“直接”从x的值来初始化。如果x也是widget类型,那么会调用拷贝构造函数,否则,调用一个转换构造函数。然而要注意,{x}语法创建了一个initializer_list,如果widget有一个带initializer_list参数的构造函数,那么这个构造函数会被优先选择。否则,如果widget有一个带任意类型x的构造函数,那么此构造函数则会被调用。
相对于(d)优先选择(e)有两个主要的区别:
第一,像(c)一样,(e)是一个没有二义性且避免了棘手的解析。如果x是一个类型名,那么(d)就是一个函数声明语句,即使在作用域范围内存在一个同名的x变量,而(e)绝不可能是个函数声明语句。
第二,(e)更安全,因为它不允许值变窄(narrowing,又名“lossy”)转换,不允许一些内置的类型,比如:
int i1( 12.345 ); // ok: toss .345, we didn't like it anyway
int i2{ 12.345 }; // error: would be lossy implicit narrowing
(f)和(g)是拷贝初始化和拷贝列表初始化
这是最后两个非auto的情况:
widget w = x; // (f)
这称为“拷贝初始化”,从概念上来说,变量w使用widget的移动或拷贝构造函数来初始化,可能在隐式地调用其它函数来转换这个参数之后。(这里不会调用explicit转换)
Common Mistake:这总是初始化,它绝不会是赋值,因此绝不会调用T::operator=()。我知道那里有个“=”字符,but don’t let that throw you。那只是一个C的延续语法,不是赋值操作。
下面是它们的语义:
· 如果x是widget类型,(f)和(d)widget w(x)的意义是相同的,除了explicit构造函数不能使用之外。它保证了只有一个构造函数被调用。
· 如果x是其他类型,从概念上来说,编译器会先隐式地将x转换成一个临时的widget对象,然后对临时右值使用移动构造函数,如果没有移动构造函数,那么使用拷贝构造函数 作为后备。假设可以隐式转换,那么(f)的意义和widget w( widget(x) );是相同的。
注意到上面说了几次“从概念上”,那是因为实际中编译器允许,且经常地优化掉临时对象,如果可以隐式转换,从(f)到(d),因此优化掉多余的移动操作。然而,即使当编译器这么做,widget的拷贝构造函数任然必须是可访问的,即使不被调用,拷贝构造函数的副作用可能会也可能不会发生,that’s all.
现在看一下追加了“=”的相关语法:
widget w = {x}; // (g)
这个称为“拷贝列表初始化”。它的意思和widget w{x};相同。除了explicit构造函数不能使用之外。它保证了只有一个构造函数被调用。
(h)和(i)都是拷贝初始化,但更简单
auto w = x; // (h)
auto w = widget{x}; // (i)
语义正如(f)和(g),除了更方便教学、学习和使用。因为使用auto保证了右手端的表达式类型会被精确地推导。要注意的是语句(i)对于隐式和显示的转换都能工作。
(h)行的意思和(d)相同,type_of_x w(x);。只有一个构造函数被调用。它保证了在程序演变过程中的一致性:因为(h)行没有承诺一个显示的类型,它同时保证了效率的最大化(因为没有涉及转换)和健壮性的最大化(因为w类型会“自动”地跟踪类型,如果程序在维护中改变的情况下)
当你想要确定一个指定的类型和需要显示转换,那么(i)行是最一致的使用手段。再一次,{}语法避免了有损收缩转换。实际上在多数编译器中,只有一个构造函数会被调用,和我们在(f)和(g)看到的一样。从概念上说调用了两个构造函数,一个转换或者一个拷贝构造函数创建一个临时的widget{x},紧接着通过移动构造将它“移动”到w,但是编译器经常会省略(优化)后者。
一般来说,我建议你尝试上面两种形式的语句。随着你习惯了它们之后就会越来越喜欢使用它们。我现在倾向于使用这种方式来声明所有局部变量。(我知道你们当中有些人会怀疑这种用法,更多关于“auto的问题”在另外一篇GotW中)。
[译]GotW #1: Variable Initialization的更多相关文章
- [译]GotW #1: Variable Initialization 续
Answer 2. 下面每行代码都做了什么? 在Q2中,我们创建了一个vector<int>且传了参数10和20到构造函数中,第一种情况下(10,20),第二种情况是{10, 20}. 它 ...
- [译]GotW #6a: Const-Correctness, Part 1
const 和 mutable在C++存在已经很多年了,对于如今的这两个关键字你了解多少? Problem JG Question 1. 什么是“共享变量”? Guru Question 2. con ...
- [译]GotW #89 Smart Pointers
There's a lot to love about standard smart pointers in general, and unique_ptr in particular. Proble ...
- [译]GotW #6b Const-Correctness, Part 2
const和mutable对于书写安全代码来说是个很有利的工具,坚持使用它们. Problem Guru Question 在下面代码中,在只要合适的情况下,对const进行增加和删除(包括 ...
- [译]GotW #4 Class Mechanics
你对写一个类的细节有多在行?这条款不仅注重公然的错误,更多的是一种专业的风格.了解这些原则将会帮助你设计易于使用和易于管理的类. JG Question 1. 什么使得接口“容易正确使用,错误使用却很 ...
- [译]GotW #3: Using the Standard Library (or, Temporaries Revisited)
高效的代码重用是良好的软件工程中重要的一部分.为了演示如何更好地通过使用标准库算法而不是手工编写,我们再次考虑先前的问题.演示通过简单利用标准库中已有的算法来避免的一些问题. Problem JG Q ...
- [译]GotW #2: Temporary Objects
不必要的和(或)临时的变量经常是罪魁祸首,它让你在程序性能方面的努力功亏一篑.如何才能识别出它们然后避免它们呢? Problem JG Question: 1. 什么是临时变量? Guru Q ...
- [译]GotW #5:Overriding Virtual Functions
虚函数是一个很基本的特性,但是它们偶尔会隐藏在很微妙的地方,然后等着你.如果你能回答下面的问题,那么你已经完全了解了它,你不太能浪费太多时间去调试类似下面的问题. Problem JG Ques ...
- 【ZZ】C++11之统一初始化语法 | 桃子的博客志
C++11之统一初始化语法 | 桃子的博客志 https://taozj.net/201710/list-initialize.html 在当前新标准C++11的语法看来,变量合法的初始化器有如下形式 ...
随机推荐
- js广告浮动
一个广告框在指定区域,有定位属性的父级区域内,一直向右向左移动,如果碰到左右边框,反向,如果碰到上下边距,反向,实现在指定框中浮动的效果. <!doctype html> <html ...
- Integer跟int的区别(备份回忆)
int与Integer的区别 int 是基本数据类型Integer是其包装类,注意是一个类.为什么要提供包装类呢???一是为了在各种类型间转化,通过各种方法的调用.否则 你无法直接通过变量转化.比如, ...
- JavaScript高级程序设计(二):在HTML中使用JavaScript
一.使用<script>元素 1.<script>元素定义了6个属性: async:可选.表示应该立即下载脚本,但不应该妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本 ...
- 最新的 cocoapods 安装与使用(2016.11)
cocoapods简介: cocoapods 是iOS的类库管理工具,可以让开发者很方便集成各种第三方库,而不用去网站上一个个下载,再一个个文件夹的拖进项目中,还得添加相关的系统依赖库.只需要安装好c ...
- Linux 进程编程
Linux通过维护者五个状态来调度进程的运行.这五个状态分别为:运行.可中断.不可中断.僵死.停止 . PID来标识不同的进程的,Linux中每一个进程都有一个唯一的进程号 . PCB块就是一个进程资 ...
- jquery api 笔记(2) 事件 事件对象
事件 #1.resize() 缩放窗体:window.resizeTo(width, height); 并不是兼容做法. #2 .scroll() ->获取滚动条的位置: .scro ...
- Spring Cloud Eureka Server 启停状态监控
目前发现如下的api: 当时没有找到文档 http://localhost:8761/eureka/apps 参考文章:(此文中api带有v2我自己试验不需要v2) http://blog.csdn. ...
- IDEA插件开发基础
由于简易ORM的需要,想要做一些代码自动生成功能(通过右键菜单辅助) 半自动编写代码,故考虑需要开发IDE插件(我司现使用IDEA) 1.例子代码http://confluence.jetbrains ...
- juery mobile select下来菜单选项提交form问题
注意: data-native-menu="false" 虽然具有渲染作用,但是无法进行js提交. <script type="text/javascript&q ...
- BestCoder Round #67 (div.2) N*M bulbs
问题描述 N*M个灯泡排成一片,也就是排成一个N*M的矩形,有些开着,有些关着,为了节约用电,你要关上所有灯,但是你又很懒. 刚好有个熊孩纸路过,他刚好要从左上角的灯泡走去右下角的灯泡,然后离开. 但 ...