在C++中使用auto声明变量是否存在缺点?

DxA*_*pha 141 c++ type-inference auto c++11

这似乎auto是一个相当重要的功能,在C++ 11中添加似乎遵循许多较新的语言.与像Python这样的语言一样,我没有看到任何显式变量声明(我不确定是否可以使用Python标准).

使用auto声明变量而不是显式声明它们有缺点吗?

Pet*_*ter 110

你只问了一些缺点,所以我突然强调其中一些.如果用得好,auto也有几个优点.这些缺点源于易于滥用,以及代码以非预期方式运行的可能性增加.

主要缺点是,通过使用auto,您不一定知道正在创建的对象的类型.在某些情况下,程序员可能希望编译器推导出一种类型,但编译器坚决推断另一种类型.

给出类似的声明

auto result = CallSomeFunction(x,y,z);
Run Code Online (Sandbox Code Playgroud)

你不一定知道什么类型result.它可能是一个int.它可能是一个指针.它可能是别的东西.所有这些都支持不同的操作.您还可以通过稍微改变来显着更改代码

auto result = CallSomeFunction(a,y,z);
Run Code Online (Sandbox Code Playgroud)

因为,取决于CallSomeFunction()结果类型存在的重载可能完全不同 - 因此后续代码的行为可能与预期完全不同.您可能会在以后的代码中突然触发错误消息(例如,随后尝试取消引用int,尝试更改现在的内容const).更加险恶的变化是你的变化超越编译器的地方,但随后的代码表现在不同的和未知的 - 可能是错误的 - 方式.

因此,没有明确知道某些变量的类型会使得更难以严格证明代码按预期工作的说法.这意味着需要更多的努力来证明在高关键性(例如安全关键或关键任务)领域中"适合目的"的主张.

另一个更常见的缺点是程序员使用它auto作为一种钝器来强制编译代码,而不是考虑代码正在做什么,并努力使其正确.

  • 有趣的是,如果这样的例子是使用"auto"的缺点,那么大多数鸭型语言在设计上都会遇到这样的缺点! (57认同)
  • 从评论中可以看出,"缺点"被解释为某种不可接受的原因.语言特征的缺点是开发人员需要考虑和证明接受与否的缺点 - 即进行工程权衡.我没有就为什么应该或不应该使用该功能提出全面的主张. (24认同)
  • @DevSolar:为什么`T CallSomeFunction(T,int,int)`是一个设计缺陷?显然,它"根据其参数的顺序返回不同的类型." (16认同)
  • 如果`CallSomeFunction()`根据其参数的顺序返回不同的类型,那就是`CallSomeFunction()`的设计缺陷,而不是`auto`的问题.如果你在使用它之前没有阅读你正在使用的函数的文档,那就是程序员的缺陷,而不是"auto"的问题. - 但我知道你在这里扮演魔鬼的拥护者,只是Nir Friedman有更好的情况. (11认同)
  • "主要的缺点是,通过使用`auto`,你不一定知道正在创建的对象的类型." 你能详细说明为什么这是'auto`的问题,而不是子表达式临时问题?为什么`auto result = foo();`bad,但是`foo().bar()`不是吗? (9认同)
  • @Peter:*更糟糕的变化是你的变化超越编译器的地方,但随后的代码表现在不同的和未知的 - 可能是错误的方式.*如果它通过编译器,它意味着你的代码类型检查.如果程序的行为发生了变化,则意味着变量的显式类型会导致隐式转换.转换应声明为"explicit",否则后续表达式将以语义不一致的方式重载.简而言之,有些东西是可疑的,你最好找出什么. (7认同)
  • 在大多数现代IDE中,当您将鼠标悬停在变量类型上时,即使是自动声明的变量类型,也会获得有关变量类型的信息 (5认同)
  • 最隐蔽的运行时问题是如果一个float被一个int替换,后来的代码会以静默方式失去精度. (3认同)
  • 我认为它也是子表达临时性的缺点. (3认同)
  • @sashoalm:这就是为什么还引入了统一初始化的原因.`int`不能从float中统一初始化. (3认同)
  • **这个.**这么多.人们嘲笑我不是一个"汽车"粉丝,但是当人们在没有实际思考的情况下将它写在任何地方时,这是一个严重的缺点. (3认同)

Nir*_*man 75

这不是auto原则上的一个缺点,但实际上对某些人来说似乎是一个问题.基本上,有些人是:a)治疗auto作为类型救世主和使用时关闭他们的大脑关闭,或B)忘了,auto总是推导值类型.这导致人们做这样的事情:

auto x = my_obj.method_that_returns_reference();
Run Code Online (Sandbox Code Playgroud)

哎呀,我们只是深深地复制了一些对象.它通常是错误或性能失败.然后,你也可以摆动另一种方式:

const auto& stuff = *func_that_returns_unique_ptr();
Run Code Online (Sandbox Code Playgroud)

现在你得到一个悬垂的参考.这些问题根本不是由问题引起的auto,所以我不认为它们是反对它的合法论据.但auto由于我在开始时列出的原因,似乎使这些问题更常见(根据我的个人经验).

我想给定的时间人们会调整,并理解分工:auto推断的基础类型,但你还是要想想参考的烦躁和常量性.但这需要一些时间.

  • @Yakk它无法安全地做到这一点,因为它可以切片.唯一安全的事情是`= delete`超载.虽然你说的更普遍是一个解决方案.如果你有兴趣,这是我探讨过的话题:http://www.nirfriedman.com/2016/01/18/writing-good-cpp-by-default-in-the-stl/. (4认同)
  • @LaurentLARIZZA:有些类只是因为有时需要复制构造函数(例如,`std :: vector`的实例).复制昂贵不是类的属性,而是单个对象的属性.因此`method_that_returns_reference`可能引用具有复制构造函数的类的对象,但复制的对象恰好相当昂贵(并且无法移动). (3认同)
  • @LaurentLARIZZA存储在向量中的东西的实例并不昂贵,只是常规例如vector <double>复制起来很昂贵,它是堆分配+ O(N)工作.移动是一个红鲱鱼.我显示的第一行将复制,而不是移动,除非返回的引用是右值引用.COW真的既不在这里也不在那里.事实是,复制对象的成本总是很高. (2认同)

Rei*_*ica 51

其他答案提到的缺点是"你真的不知道变量的类型是什么." 我会说这很大程度上与代码中的草率命名约定有关.如果您的接口名称清晰,则无需关心确切的类型.当然,auto result = callSomeFunction(a, b);不要告诉你太多.但是auto valid = isValid(xmlFile, schema);告诉你足够使用valid而不必关心它的确切类型.毕竟,只是if (callSomeFunction(a, b)),你也不会知道这种类型.与任何其他子表达式临时对象相同.所以我不认为这是一个真正的缺点auto.

我要说它的主要缺点是,有时,确切的返回类型不是你想要使用的.实际上,有时实际返回类型与"逻辑"返回类型不同,作为实现/优化细节.表达模板是一个很好的例子.假设我们有这个:

SomeType operator* (const Matrix &lhs, const Vector &rhs);
Run Code Online (Sandbox Code Playgroud)

从逻辑上讲,我们希望SomeType如此Vector,我们绝对希望在我们的代码中对待它.但是,出于优化目的,我们使用的代数库可以实现表达式模板,实际的返回类型是这样的:

MultExpression<Matrix, Vector> operator* (const Matrix &lhs, const Vector &rhs);
Run Code Online (Sandbox Code Playgroud)

现在,问题是很MultExpression<Matrix, Vector>可能存储const Matrix&const Vector&内部存储; 它期望它将Vector在其完整表达式结束之前转换为a .如果我们有这个代码,一切都很好:

extern Matrix a, b, c;
extern Vector v;

void compute()
{
  Vector res = a * (b * (c * v));
  // do something with res
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我们在auto这里使用过,我们可能遇到麻烦:

void compute()
{
  auto res = a * (b * (c * v));
  // Oops! Now `res` is referring to temporaries (such as (c * v)) which no longer exist
}
Run Code Online (Sandbox Code Playgroud)

  • @NirFriedman你是对的它很强大,但我实际上觉得'auto`有很少的缺点,所以我坚持这种力量.代理等的其他示例包括各种"字符串构建器"和在DSL中找到的类似对象. (3认同)
  • 我之前被表达模板和`auto`咬了,特别是使用了Eigen库.这特别棘手,因为问题通常不会出现在调试版本中. (2认同)
  • _"如果您的接口名称清楚,则不需要关心确切的类型"_您的编译器无法通过研究变量的名称来检查代码的正确性.这是类型系统的全部要点.盲目地绕过它是愚蠢的! (2认同)
  • @Angew:对于临时变量来说这不是问题,因为您通常会立即使用它们,而没有 `auto` 通常会涉及某种类型检查(并且在任何地方都使用 `auto` 会像它一样消除这种类型的安全性其他地方)。这不是一个很好的比较。 (2认同)

ks1*_*322 13

唯一的缺点是,有时你不能声明const_iteratorauto.你会得到普通(非const的)迭代器在取自代码这个例子中这个问题:

map<string,int> usa;
//...init usa
auto city_it = usa.find("New York");
Run Code Online (Sandbox Code Playgroud)

  • 好吧,你得到一个`iterator`,因为你的地图是非`constst`.如果你想将它转换为`const_iterator`,或者像往常一样明确指定变量类型,或者提取一个方法,使你的map在你的`find`的上下文中是const.(我更喜欢后者.SRP.) (3认同)

Ska*_*kam 11

它使您的代码更难或更乏味,无法阅读.想象一下这样的事情:

auto output = doSomethingWithData(variables);
Run Code Online (Sandbox Code Playgroud)

现在,要弄清楚输出的类型,你必须追踪doSomethingWithData功能的签名.

  • 不总是.`auto it = vec.begin();`比`std :: vector <std :: wstring> :: iterator it = vec.begin();`更容易阅读. (40认同)
  • "追踪功能签名",如果这不是鼠标悬停或按键按下("跟随符号"/"转到声明"/无论它是什么),您需要更多地配置编辑器或切换到可以在没有配置的情况下执行此操作的IDE ...但您的观点仍然有效. (6认同)
  • 我注意到在查看签到时,不是在IDE中,而是在小巧的差异中!对于auto,由于这个原因,他们更难阅读. (6认同)
  • 同意.这取决于用例.我可以更准确地说明这一点. (4认同)
  • 如果`doSomethingWithData` 的作者愿意调用他的方法`crossProductOf(variables)`,那么返回的类型将是无关紧要的。 (2认同)

Meh*_*dad 9

这个开发者一样,我讨厌auto.或者更确切地说,我讨厌人们如何滥用auto.

我(强烈)意见auto是帮助你编写通用代码,而不是减少输入.
C++是一种语言,其目标是让您编写健壮的代码,而不是最小化开发时间.
从C++的许多功能来看,这是相当明显的,但不幸的是,一些较新的类似auto减少输入误导人们认为他们应该开始懒惰打字.

auto过去,人们使用typedefs,这很棒,因为typedef 允许图书馆的设计师帮助你弄清楚返回类型应该是什么,以便他们的图书馆按预期工作.当你使用时auto,你从类的设计者那里拿走那个控件,而不是要求编译器弄清楚应该是什么类型,这会从工具箱中删除一个最强大的C++工具,并有可能破坏他们的代码.

通常,如果你使用auto,那应该是因为你的代码适用于任何合理的类型,而不是因为你只是懒得写下它应该使用的类型.如果你auto用作帮助懒惰的工具,那么你最终会开始在程序中引入微妙的错误,这通常是由于你使用过而没有发生的隐式转换引起的auto.

不幸的是,这些错误是很难说明在很短的例子在这里,因为他们的简洁使他们比上来的用户项目的实际例子那么令人信服-不过,他们在期待某一模板的重码容易出现隐式转换采取地点.

如果你想要一个例子,有一个在这里.但需要注意的是:在尝试跳跃并批评代码之前:请记住,围绕这种隐式转换开发了许多知名且成熟的库,并且它们存在,因为它们解决了即使不是不可能也很难解决的问题否则解决.在批评之前尝试找出更好的解决方案.

  • `这很棒,因为typedef允许库的设计者帮助你弄清楚返回类型应该是什么,以便他们的库按预期工作.当你使用auto时,你从类的设计者那里取走了那个控件,而是要求编译器弄清楚应该是什么类型`IMO并不是一个很好的理由.最新的IDE,例如Visual Studio 2015,允许您通过将鼠标悬停在"auto"上来检查变量的类型.这是_*与*typedef`完全相同*. (3认同)

Lau*_*ZZA 6

auto没有缺点本身,我提倡(手波浪)在新代码中任何地方使用它.它允许您的代码始终进行类型检查,并始终避免静默切片.(如果B派生自A并且函数返回A突然返回B,则auto表现为按预期存储其返回值)

虽然,预C++ 11遗留代码可能依赖于使用显式类型变量引起的隐式转换.将显式类型变量auto更改为可能会更改代码行为,因此您最好小心谨慎.