新关键字"auto" ;什么时候应该使用它来声明变量类型?

The new keyword "auto"; When should it be used to declare a variable type?

本文关键字:声明 类型 变量 auto 关键字 什么时候      更新时间:2023-10-16

可能的重复项:
使用 C++0x 自动关键字多少才算太多

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

我真正想要的是关于以下方面的最佳实践指南

  • 何时使用自动
  • 何时应避免

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

作为上下文,这个问题是由我在这里的回答引发

我认为当该类型在您的项目中工作(或将要工作(的联合程序员中非常知名时,则可以使用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可能会根据上下文增加或减少代码的清晰度和可读性。如果上下文明确了它是什么类型,或者至少应该如何使用它(在标准容器迭代器的情况下(,或者甚至不需要实际类型的知识(例如在表达式模板中(,那么应该使用auto,如果上下文没有明确说明并且不是很常见(例如上面的第二种情况(, 那么最好避免它。

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

for (auto i : some_container) {
   ...

我在这里关心的只是i是容器中的任何内容。

这有点像typedefs。

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,你会构造一个所需类型的对象。

如果可以在不引入副作用的情况下删除代码中的一些冗余,那么这样做一定很好。

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

尽管如此,我仍然坚持认为(特别是在静态类型语言中(编译器在为我们跟踪类型方面比我们好得多。大多数时候,确切的类型并不是特别重要(否则接口在实践中将无法工作(。更重要的是了解允许哪些操作。上下文应该告诉我们这一点。

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

原则上,编写auto x = y;可以防止此问题。

另一方面,应该

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

并非所有案例都如此明确,但我坚持认为大多数案例都是如此,而且

    在大多数情况下,很容易
  1. 看出是否需要知道显式类型,并且
  2. 显式类型的需要相对较少。

C#编译器团队的首席开发人员Eric Lippert在var方面也发表了同样的看法。

我认为你第一个问题的答案是否定的。我们知道的足够多,可以整理一些关于何时使用或避免auto的指导方针,但它们仍然留下了很多我们目前可以说的最好的情况是,我们还不能以客观的方式给出太多关于它们的建议。

您几乎必须使用它的明显情况是在模板中,当您希望(例如(正确的类型来保存对两个泛型参数的某种操作的结果时。在这种情况下,滥用的唯一可能性并不是滥用auto本身,而是您正在执行的一般操作类型(或您正在编写的模板类型等(是否是您最好避免的事情。

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

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

不过,这仍然留下了很多判断电话的情况。对于这些情况,我自己的倾向是将auto视为默认值,并且仅在至少有点类似于我上面引用的后一种情况的情况下使用显式类型 - 即使正确操作不需要特定类型,我真的想要一个特定类型,即使这可能涉及隐式转换。

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

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

我使用过具有完整类型推理的语言。我认为没有理由不把auto放在技术上可行的任何地方*。事实上我可能已经写过auto i = 0;,其中intauto短一个字符。我什至不确定我是否做到了,因为底部是:我不在乎清单类型。

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

仅将其用于长重复类型,例如长模板和 lambda 函数类型。如果可以的话,尽量避免它,把事情说清楚。