105

可能重复:
C++0x 自动关键字多少是太多了

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

我真正在寻找的是最佳实践指南

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

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

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

4

6 回答 6

170

我认为当该类型在您的项目中工作(或将工作)的共同程序员中非常知名时,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另一个可以使用的地方是当你使用new1make_*functions 时,比如这里:

//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应该使用,并且如果上下文没有说清楚并且不是很常见(例如上面的第二种情况),那么最好避免使用它。

于 2011-08-01T15:13:36.570 回答
21

简单的。当您不在乎类型是什么时使用它。例如

for (auto i : some_container) {
   ...

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

这有点像 typedef。

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

在这里,我不在乎handw是 floats 还是 doubles,只关心它们是适合表达 heights 和 weights 的任何类型

或者考虑

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

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

此外,无法拼写 lambdas 的类型,所以auto f = []...是好的风格。另一种方法是强制转换,std::function但这会带来开销。

我真的无法想象auto. 我能想象的最接近的是剥夺自己对某种重要类型的显式转换——但你不会使用auto它,你会构造一个所需类型的对象。

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

于 2011-08-01T15:27:58.033 回答
17

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

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

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

写作原则上auto x = y;可以避免这个问题。

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

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

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

C# 编译器团队的首席开发人员Eric Lippertvar.

于 2011-08-01T15:28:30.160 回答
5

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

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

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

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

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

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

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

于 2011-08-01T16:52:53.847 回答
2

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

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

于 2011-08-01T15:52:45.803 回答
1

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

于 2011-08-01T15:14:19.710 回答