新关键字“自动”;什么时候应该用它来声明一个变量类型?

时间:2011-08-01 15:09:00

标签: c++ c++11 auto type-safety

  

可能重复:
  How much is too much with C++0x auto keyword

我们(作为社区)是否有足够的经验来确定何时和/或是否滥用汽车?

我真正想要的是

上的最佳做法指南
  • 何时使用自动
  • 什么时候应该避免

简单的经验法则可以在80%的情况下快速遵循。

作为上下文,我的回复here

引发了这个问题

6 个答案:

答案 0 :(得分:140)

我认为当这个类型在您的项目中工作(或将工作)的共同程序员中非常有名时,可以使用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
{
      //..
}

但是当这种类型不是很熟知并且不经常使用时,我认为auto似乎会降低可读性,例如:

//bad : auto decreases readability here
auto obj = ProcessData(someVariables);

虽然在前一种情况下,auto的使用似乎非常好并且不会降低可读性,因此可以广泛使用,但在后一种情况下,它会降低可读性,因此不应该使用


可以使用auto的另一个地方是当您使用new 1 make_*函数时,例如:

//without auto. Not that good, looks cumbersome
SomeType<OtherType>::SomeOtherType * obj1 = new SomeType<OtherType>::SomeOtherType();
std::shared_ptr<XyzType> obj2 = std::make_shared<XyzType>(args...);
std::unique_ptr<XyzType> obj2 = std::make_unique<XyzType>(args...);

//With auto. good : auto increases readability here
auto obj1 = new SomeType<OtherType>::SomeOtherType();
auto obj2 = std::make_shared<XyzType>(args...);
auto obj3 = std::make_unique<XyzType>(args...);

这里非常好,因为它减少了键盘的使用,而不会降低可读性,因为任何人都可以通过查看代码来了解正在创建的对象的类型。

<子> 1。尽管避免使用new和原始指针。


有时,类型是如此无关紧要,甚至不需要类型的知识,例如表达模板;事实上,实际上不可能写出类型(正确),在这种情况下auto对程序员来说是一种解脱。我编写了表达式模板库,可以用作:

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会让您的生活更轻松。上面使用的表达式非常简单;考虑一些更复杂的表达式的类型:

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 );

这种表达式的类型会更加庞大和丑陋,但是由于auto,我们现在可以让编译器推断出表达式的类型。


所以底线是:关键字auto可能增加或减少代码的清晰度和可读性,取决于上下文。如果上下文清楚地表明它是什么 type ,或者至少应该如何使用它(在标准容器迭代器的情况下)或者甚至不需要实际类型的知识(例如在表达式中)模板),那么auto应该使用,如果上下文不清楚并且不常见(例如上面的第二种情况),那么最好是避免

答案 1 :(得分:16)

易。当您不关心类型是什么时使用它。例如

for (auto i : some_container) {
   ...

我所关心的只是i是容器中的任何内容。

有点像typedef。

typedef float Height;
typedef double Weight;
//....
Height h;
Weight w;

在这里,我不关心hw是浮动还是双打,只是它们适合表达高度和重量的任何类型

或考虑

for (auto i = some_container .begin (); ...

这里我关心的是它是一个合适的迭代器,支持operator++(),就像在这方面打字一样。

同样不能拼写lambda的类型,因此auto f = []...是好的风格。替代方案是转向std::function,但这会带来开销。

我无法想象auto的“滥用”。我能想象到的最接近的是剥夺了自己对某种重要类型的显式转换 - 但你不会使用auto,你可以构造一个所需类型的对象。

如果您 可以 删除代码中的某些冗余而不会产生副作用,那么 必须 是好的这样做。

答案 2 :(得分:14)

我在C#中应用与var相同的规则:自由使用。它增加了可读性。除非变量的类型实际上足够重要,否则应明确说明,在这种情况下应该这样做(duh)。

尽管如此,我仍然认为(特别是在静态类型语言中)编译器在跟踪我们的类型方面比我们更好。大多数情况下,完全类型无论如何都不是非常重要(否则接口在实践中不起作用)。了解允许哪些操作更为重要。上下文应告诉我们。

此外,auto实际上可以通过防止初始化中不必要的隐式转换来防止错误。通常,如果Foo x = y;不是y类型并且存在隐式转换,则语句Foo将执行隐式转换。这是避免首先进行隐式转换的原因。不幸的是,C ++已经有太多了。

撰写auto x = y;可以在原则 中阻止此问题

另一方面,应该清楚的是,当我执行假定这个或整数字节数的计算时,变量的显式类型必须是已知的并且应该清楚地说明。

并非所有案例都是明确的,但我认为大多数情况都是如此,

  1. 在大多数情况下,很容易看出是否需要知道显式类型,
  2. 对显式类型的需求相对较少。
  3. Eric Lippert,C#编译器团队的主要开发人员,has stated much the same with regards to var

答案 3 :(得分:4)

我认为你的第一个问题的答案是否定的。我们已经足够了解一些关于何时使用或避免使用auto的指导原则,但是他们仍然留下了一些我们目前可以说的最好的情况,即我们还不能给出客观建议的方法。关于他们。

当您希望(例如)正确的类型在两个通用参数上保存某些操作的结果时,您几乎 使用它的明显情况是在模板中。在这样的情况下,滥用的唯一可能性实际上并不是滥用auto本身,而是您正在进行的一般操作类型(或您正在编写的模板类型等)是你最好避免的事情。

至少还有一些情况显然需要避免auto。如果您正在使用类似代理类型的内容,而您依赖于从代理 - >目标转换来完成手头的部分工作,auto将(尝试)创建相同类型的目标作为源,以便不会发生转换。在某些情况下,这可能只会延迟转换,但在其他情况下它根本不起作用(例如,如果代理类型不支持赋值,通常就是这种情况)。

另一个例子是当你需要确保特定变量具有特定类型时,为了类似外部接口。例如,考虑将网络掩码应用于IP(v4)地址。为了论证,让我们假设您正在使用地址的各个八位字节(例如,将每个字节代表为unsigned char),因此我们最终得到类似octets[0] & mask[0]的内容。感谢C的类型提升规则,即使两个操作数都是unsigned char s,结果通常也是int。我们需要结果是unsigned char虽然(即,一个八位字节)不是int(通常是4个八位字节)。因此,在这种情况下,auto几乎肯定是不合适的。

尽管如此,这仍然会留下很多情况。对于这些案例,我自己的倾向是将auto视为默认值,并且仅在至少与我上面引用的后一种情况相似的情况下使用显式类型 - 即使某个特定类型不需要需要来进行正确的操作,我确实想要特定类型,即使这可能涉及隐式转换。

我的猜测(但这只是猜测)是随着时间的推移,我可能更倾向于那个方向。随着我越来越习惯于编译器选择类型,我会发现我目前认为的相当多的情况我应该指定类型,我真的不需要和代码将没事。

我怀疑我们中的很多人(而且年龄越大/经验越丰富,我们可能会更糟糕)会使用显式类型,原因最终会追溯到某种对性能的感觉,并相信我们的选择将提高性能。部分时间我们可能甚至是正确的 - 但是正如我们大多数人都有这么多经验所发现的那样,我们的猜测往往是错误的(特别是当它们基于隐含的假设时),以及编译器和随着时间的推移,处理器通常会在这些事情上变得更好。

答案 4 :(得分:2)

仅使用长重复类型(如长模板和lambda函数类型)。如果你能说清楚的话,尽量避免使用它。

答案 5 :(得分:2)

我使用的是完全类型推断的语言。我认为没有理由不将auto置于技术上可行的任何地方*。事实上,我可能已经写过auto i = 0;,其中intauto短一个字符。我甚至不确定我做了什么,因为底部是:我不关心明显打字。

*:例如auto int[] = { 0, 1, 2, 3 }不起作用。