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

Mar*_*ork 88 c++ type-safety auto c++11

可能重复:
使用C++ 0x auto关键字多少钱

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

我真正想要的是最佳实践指南

  • 什么时候使用auto
  • 何时应该避免

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

由于上下文这个问题由我的回应引发了这里

Naw*_*waz 144

我认为当类型在您的项目中工作(或将工作)的共同程序员中非常有名时,auto可以使用,例如在以下代码中:

//good : auto increases readability here
for(auto it = v.begin(); it != v.end(); ++it) //v is some [std] container
{
      //..
}
Run Code Online (Sandbox Code Playgroud)

或者,更一般地说,

//good : auto increases readability here
for(auto it = std::begin(v); it != std::end(v); ++it)//v could be array as well
{
      //..
}
Run Code Online (Sandbox Code Playgroud)

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

//bad : auto decreases readability here
auto obj = ProcessData(someVariables);
Run Code Online (Sandbox Code Playgroud)

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


auto可以使用的另一个地方是当你使用new1make_*函数时,例如:

//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...);
Run Code Online (Sandbox Code Playgroud)

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

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; 
Run Code Online (Sandbox Code Playgroud)

输出:

0, 0
1, 1
4, 8
9, 27
16, 64
Run Code Online (Sandbox Code Playgroud)

现在将上面的代码与以下不使用的等效代码进行比较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; 
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,在这种情况下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 );
Run Code Online (Sandbox Code Playgroud)

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


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

  • 我知道这已经6岁了,但我仍然在你的方形/立方体例子中大笑.从1997年开始回到c ++和c ++ 11(14)是令人愉快的. (3认同)
  • 这是使*stackoverflow*成为我的第一个goto引用的答案类型 (2认同)

spr*_*aff 16

简单.当你不在乎什么类型时使用它.例如

for (auto i : some_container) {
   ...
Run Code Online (Sandbox Code Playgroud)

我所关心的就是i容器中的任何东西.

这有点像typedef.

typedef float Height;
typedef double Weight;
//....
Height h;
Weight w;
Run Code Online (Sandbox Code Playgroud)

在这里,我不在乎是否hw是浮动还是双打,只是他们是什么类型适合于表达高度和重量.

或考虑一下

for (auto i = some_container .begin (); ...
Run Code Online (Sandbox Code Playgroud)

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

lambda的类型也不能拼写,所以auto f = []...风格也很好.替代方案是铸造,std::function但随之而来的是开销.

我无法想象一个"虐待" auto.我能想象到的最接近的是剥夺了自己对某种重要类型的显式转换 - 但你不会使用auto它,你构建了一个所需类型的对象.

如果你可以删除代码中的一些冗余而不引入副作用,那么这样做一定很好.


Kon*_*lph 14

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

不过,我坚持认为(特别是在静态类型语言中)编译器在跟踪我们的类型方面要比我们好得多.大多数情况下,确切类型无论如何都不是非常重要(否则界面在实践中不起作用).了解允许哪些操作更为重要.上下文应该告诉我们.

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

写作原则上auto x = y;可以防止这个问题.

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

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

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

C#编译器团队的首席开发人员Eric Lippert 对此表达了同样的看法var.

  • @Gene请,但这简直难以置信.如果你想*转换,你可以使用它.如果你写'auto`,你显然不希望任何转换发生. (7认同)
  • @Gene No.这两个人没有任何关系.隐式转换所带来的危险根本不存在于隐式类型变量中.隐式转换是有问题的*只是因为*它们隐藏了可能无意的语义动作.`auto`没有这样的事情. (5认同)
  • *`auto` 实际上可以通过防止不需要的隐式转换来防止错误* -- 当隐式转换实际上是*想要的时,它也可以很容易地引入错误* (3认同)
  • 我不确定我是否相信你的论点(仍在思考)。但你认为 auto 的大量使用有助于代码的可读性吗?在我看来,代码的维护比开发持续的时间要长得多,因为对于下一个人来说,这种可读性至关重要。 (2认同)

Jer*_*fin 5

我认为您第一个问题的答案是“否”。我们足够了解有关何时使用或避免使用的一些准则auto,但是它们仍然留下了很多情况,目前我们能说的最好的是我们仍然无法以客观的方式就它们提供建议。

当您想要(例如)适当的类型来保存对两个通用参数进行某些运算的结果时,几乎必须使用它的明显情况是在模板中。在这种情况下,滥用的唯一可能性实际上并不是滥用auto自身,而是您正在执行的一般操作类型(或正在编写的模板类型,等等)是否可行。最好避免。

至少在某些情况下,您显然需要避免auto。如果您使用的是类似代理类型的东西,而您要依靠从proxy-> target进行的转换来完成手头的一部分工作,auto则将(尝试)创建与源类型相同的目标,以便进行转换不会发生 在某些情况下,这可能会延迟转换,但在另一些情况下,它将根本无法工作(例如,如果代理类型不支持分配,通常就是这种情况)。

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

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

我的猜测(但这只是猜测)是随着时间的流逝,我可能会朝着这个方向发展。随着我越来越习惯于编译器选择类型,我会发现很多情况下我认为我现在应该指定类型,我真的不需要,而且代码也就很好了。

我怀疑我们很多人(年龄较大/经验丰富的人,可能会更糟)会使用显式类型,其原因最终可以追溯到对性能的某种感觉,并相信我们的选择会改善性能。在某些时候,我们甚至可能是对的-但正如我们大多数拥有丰富经验的人所发现的那样,我们的猜测通常是错误的(尤其是当它们基于隐式假设时),并且编译器和处理器通常会在这种情况下变得更好随着时间的流逝。