新的关键字“自动”; 什么时候应该用来声明一个variablestypes?
可能重复:
用C ++ 0x auto关键字多less钱
我们(作为一个社区)是否有足够的经验来确定汽车何时和/或是否被滥用?
我真正想要的是一个最佳实践指南
- 何时使用自动
- 何时应该避免
简单的经验法则可以在80%的案件中迅速得到遵守。
作为一个背景,这个问题是由我在这里的回应引发的
我认为当这个types在项目中工作(或者工作)的协程序员中非常有名,那么可以使用auto
,比如下面的代码:
//good : auto increases readability here for(auto it = v.begin(); it != v.end(); ++it) //v is some [std] container { //.. }
或者更一般地说,
//good : auto increases readability here for(auto it = std::begin(v); it != std::end(v); ++it)//v could be array as well { //.. }
但是当这个types不是很知名和不常用,那么我认为auto
似乎降低了可读性,比如这里:
//bad : auto decreases readability here auto obj = ProcessData(someVariables);
而在前一种情况下, auto
的使用看起来非常好,不会降低可读性,因此可以广泛使用,但在后一种情况下,它降低了可读性,因此不应使用。
另一个可以使用auto
地方是当你使用new
,比如这里:
//without auto. Not that good, looks cumbersome SomeType<OtherType>::SomeOtherType * pObject = new SomeType<OtherType>::SomeOtherType(); //With auto. good : auto increases readability here auto pObject = new SomeType<OtherType>::SomeOtherType();
这是非常好的,因为它减less了键盘的使用,而不会降低可读性,因为任何人只要通过查看代码就可以知道pObject
的types。
有时候,这种types是如此的不相干,甚至不需要types的知识,比如在expression式模板中 ; 事实上, 实际上不可能写出正确的types,在这种情况下, auto
对于程序员是一种解脱。 我写了expression式模板库,可以用作:
foam::composition::expression<int> x; auto s = x * x; //square auto c = x * x * x; //cube for(int i = 0; i < 5 ; i++ ) std::cout << s(i) << ", " << c(i) << std::endl;
输出:
0, 0 1, 1 4, 8 9, 27 16, 64
现在比较上面的代码和下面的不使用auto
等价代码:
foam::composition::expression<int> x; //scroll horizontally to see the complete type!! foam::composition::expression<foam::composition::details::binary_expression<foam::composition::expression<int>, foam::composition::expression<int>, foam::operators::multiply>> s = x * x; //square foam::composition::expression<foam::composition::details::binary_expression<foam::composition::expression<foam::composition::details::binary_expression<foam::composition::expression<int>, foam::composition::expression<int>, foam::operators::multiply> >, foam::composition::expression<int>, foam::operators::multiply>> c = x * x * x; //cube for(int i = 0; i < 5 ; i++ ) std::cout << s(i) << ", " << c(i) << std::endl;
正如你所看到的,在这种情况下, auto
让你的生活更加容易。 上面使用的expression式非常简单, 考虑一些更复杂的expression式的types:
auto a = x * x - 4 * x + 4; auto b = x * (x + 10) / ( x * x+ 12 ); auto c = (x ^ 4 + x ^ 3 + x ^ 2 + x + 100 ) / ( x ^ 2 + 10 );
这种expression式的types会更加巨大和丑陋,但是由于auto
,我们现在可以让编译器推断expression式的types。
所以底线是:关键字auto
可能会增加或减less代码的清晰度和可读性, 具体取决于上下文 。 如果上下文清楚地说明了它是什么types ,或者至less应该如何使用(在标准容器迭代器的情况下),或者甚至不需要知道实际types(例如在expression式模板中),那么auto
应该是使用 ,如果上下文没有说清楚,也不是很常见(比如上面第二种情况),那么最好避免 。
简单。 当你不关心types是什么时使用它。 例如
for (auto i : some_container) { ...
我所关心的只是i
在容器里的任何东西。
这有点像typedefs。
typedef float Height; typedef double Weight; //.... Height h; Weight w;
在这里,我不在乎h
和w
是浮动还是双打,只是它们是适合expression高度和重量的types 。
或考虑
for (auto i = some_container .begin (); ...
在这里,我所关心的是它是一个合适的迭代器,支持operator++()
,就像在这方面的鸭子打字一样。
此外,lambda的types不能拼写,所以auto f = []...
是好风格。 另一种方法是转换为std::function
但是会带来开销。
我不能真正想到auto
的“滥用”。 我能想象到的最接近的做法是剥夺自己对某种重要types的显式转换 – 但是您不会使用auto
来创build所需types的对象。
如果您可以在不引入副作用的情况下删除代码中的冗余,那么这样做一定是件好事。
我会在C#中应用与var
相同的规则:大量使用它 。 它增加了可读性。 除非variables的types实际上足够重要,否则不能明确说明,在这种情况下应该这样做(duh)。
不过,我认为(特别是在静态types语言中),编译器在跟踪types方面比我们好得多。 大多数情况下, 确切types不是非常重要的(否则界面在实践中将不起作用)。 了解哪些操作是允许的更重要。 上下文应该告诉我们。
此外, auto
实际上可以防止错误 ,防止初始化中不需要的隐式转换。 一般来说, Foo x = y;
将执行隐式转换,如果y
不是Foo
types并存在隐式转换。 这是避免首先隐式转换的原因。 不幸的是,C ++已经太多了。
写auto x = y;
原则上会防止这个问题。
另一方面,应该清楚的是,当我在执行计算时假定这个或者整数字节的数量,variables的显式types必须是已知的,并且应该清楚地说明。
并不是所有的案件都是明确的裁决,但是我认为大多数案件都是这样的
- 在大多数情况下,很容易看出明确的types是否需要知道
- 显式types的需求相对较less。
C#编译器团队的首席开发人员Eric Lippert 对于var
说法大同小异 。
我想你的第一个问题的答案是不是。 我们足够了解一些关于什么时候使用或避免auto
指导方针,但是他们仍然留下了很多我们现在可以说的最好的情况,就是我们还不能给予客观的build议。
当你想要(例如)适当的types来保存两个generics参数的某些操作的结果时,几乎不得不使用它的明显情况就是在模板中。 在这样的情况下,滥用的唯一可能性不会真的是滥用auto
本身,而是你正在做的一般操作types(或者你正在写的模板的types等)是否是你想要的避免更好。
至less有一些情况下,你显然需要避免auto
。 如果您使用的是类似代理types的代理types,则需要根据从代理 – >目标转换来完成部分工作, auto
会(尝试)创build与源代码types相同的目标,以便转换不会发生。 在某些情况下,这可能会延迟转换,但在其他情况下根本无法工作(例如,如果代理types不支持分配,通常情况下)。
另一个例子是,当你需要确保一个特定的variables为了类似于外部接口而具有特定的types时。 举个例子,考虑将networking掩码应用到IP(v4)地址。 为了说明起见,我们假设你正在使用地址的单个八位字节(例如,将每个unsigned char
表示为一个unsigned char
),所以我们最终得到了octets[0] & mask[0]
。 由于C的types提升规则,即使两个操作数都是unsigned char
,结果通常是int
。 我们需要的结果是一个unsigned char
尽pipe(即一个八位字节)不是一个int
(通常是4个八位字节)。 因此,在这种情况下, auto
几乎肯定是不合适的。
尽pipe如此,这仍然留下了很多案件。 我个人倾向于这种情况是把auto
当作默认值,只在显式types的情况下,至less有点类似于上面我提到的后一种情况 – 即使不需要特定的types操作,我真的想要一个特定的types,即使这可能涉及隐式转换。
我的猜测(但这只是一个猜测)是,随着时间的推移,我可能更倾向于这个方向。 当我习惯了编译器挑选types时,我会发现有相当多的情况下,我现在认为我应该指定types,我真的不需要,代码将会很好。
我怀疑我们中的很多人(以及我们年纪较大/经验丰富,可能更糟糕的是我们会这样做)会使用显式types的原因,最终追溯到对性能的一些感觉,并相信我们的select会提高性能。 部分时间我们甚至可能是对的 – 但是正如我们大多数有这么多经验的人所发现的那样,我们的猜测常常是错误的(特别是当它们基于隐含的假设时),编译器和处理器通常在这些事情上变得更好随着时间的推移。
我已经使用完整types推理的语言。 我认为没有理由不把auto
放在任何地方,这在技术上是可行的*。 其实我可能已经写了auto i = 0;
,其中int
是一个比auto
短的字符。 我甚至不知道我做了什么,因为底部是:我不关心明确的打字。
*:例如auto int[] = { 0, 1, 2, 3 }
不起作用。
只能使用长重复types,如长模板和lambda函数types。 尽量避免它,如果你能弄清楚事情。