C ++ 11 auto关键字多less钱?
我一直在C ++ 11标准中使用新的auto
关键字,用于复杂的模板types,这是我相信它的目的。 但我也使用它的东西,如:
auto foo = std::make_shared<Foo>();
更怀疑的是:
auto foo = bla(); // where bla() return a shared_ptr<Foo>
我没有看到有关这个话题的很多讨论。 看起来auto
可能被过度使用,因为types通常是一种文件和健全性检查。 你在哪里画线使用auto
和什么是这个新function的推荐用例?
澄清:我不是要求哲学观点, 我要求标准委员会对这个关键词进行预期的使用,可能会对实际中如何实现这个预期的使用做出评论。
附注:这个问题被转移到SE.Programmers,然后回到堆栈溢出。 关于这个的讨论可以在这个元问题中find。
我觉得应该使用auto
关键字,但是很难说如何写一个types,但是expression式右边的types是显而易见的。 例如,使用:
my_multi_type::nth_index<2>::type::key_type::composite_key_type::\ key_extractor_tuple::tail_type::head_type::result_type
在boost::multi_index
获得复合键types,即使你知道它是int
。 你不能只写int
因为将来可能会改变它。 我会在这种情况下写auto
。
所以如果auto
关键字提高了在特定情况下的可读性,然后使用它。 当读者明显知道什么types的auto
代表时,你可以写auto
。
这里有些例子:
auto foo = std::make_shared<Foo>(); // obvious auto foo = bla(); // unclear. don't know which type `foo` has const size_t max_size = 100; for ( auto x = max_size; x > 0; --x ) // unclear. could lead to the errors // since max_size is unsigned std::vector<some_class> v; for ( auto it = v.begin(); it != v.end(); ++it ) // ok, since I know // that `it` has an iterator type (don't really care which one in this context)
在任何地方都可以使用auto
,特别是const auto
这样副作用就不那么重要了。 除了在明显的情况下,你不必担心types,但是它们仍然会被静态validation,你可以避免一些重复。 在auto
不可行的情况下,可以使用decltype
将语义types表示为基于expression式的合同 。 你的代码看起来不一样,但这将是一个积极的变化。
简单。 当你不关心types是什么时使用它。 例如
for (const auto & i : some_container) { ...
我所关心的只是i
在容器里的任何东西。
这有点像typedefs。
typedef float Height; typedef double Weight; //.... Height h; Weight w;
在这里,我不在乎h
和w
是浮动还是双打,只是它们是适合expression高度和重量的types 。
或考虑
for (auto i = some_container .begin (); ...
在这里,我所关心的是它是一个合适的迭代器,支持operator++()
,就像在这方面的鸭子打字一样。
此外,lambda的types不能拼写,所以auto f = []...
是好风格。 另一种方法是转换为std::function
但是会带来开销。
我不能真正想到auto
的“滥用”。 我能想象到的最接近的做法是剥夺自己对某种重要types的显式转换 – 但是您不会使用auto
来创build所需types的对象。
如果您可以在不引入副作用的情况下删除代码中的冗余,那么这样做一定是件好事。
反例(借用别人的答案):
auto i = SomeClass(); for (auto x = make_unsigned (y); ...)
在这里,我们关心什么是types,所以我们应该写Someclass i;
和for(unsigned x = y;...
去吧。 在任何地方使用auto
,使编写代码更容易
任何语言的每个新function都会被至less某些types的程序员滥用。 只有通过一些有经验的程序员(而不是新手)适度的过度使用,其他有经验的程序员才能学习正确使用的界限。 极端的过度使用通常是不好的,但可能是好的,因为这种过度使用可能会导致function的改进或更好的替代function。
但是,如果我正在处理的代码超过几行像
auto foo = bla();
types被指示零次,我可能想要改变这些行以包括types。 第一个例子很好,因为这个types被声明了一次,并且auto
避免了我们不得不两次编写混乱的模板types。 用于C ++++的Hooray。 但是明确地显示types为零的时间,如果在附近的行中不容易看到,那么至less在C ++及其后续版本中会让我感到紧张。 对于其他语言,devise更高层次更抽象,多态性和通用性,没关系。
是的,它可以被滥用,不利于可读性。 我build议在上下文中使用它,在这些上下文中,精确的types很长,或者是不可改变的,或者对于可读性来说不重要,variables是短暂的。 例如,迭代器types通常很长并且不重要,所以auto
会起作用:
for(auto i = container.begin(); i != container.end(); ++i);
auto
在这里不伤害可读性。
另一个例子是parsing器规则types,它可以是漫长而复杂的。 比较:
auto spaces = space & space & space;
同
r_and_t<r_and_t<r_char_t<char>&, r_char_t<char>&>, r_char_t<char>&> spaces = space & space & space;
另一方面,当types是已知的并且简单的时候,如果明确地陈述的话会好得多:
int i = foo();
而不是
auto i = foo();
在“ Ask Us Anything”面板的C ++和Beyond 2012中, Andrei Alexandrescu,Scott Meyers和Herb Sutter谈论了何时使用而不使用auto
的奇妙交stream 。 跳到分钟25:03进行4分钟的讨论。 三位演讲嘉宾都给出了不应该使用auto
时应该注意的一些要点。
我高度鼓励人们自己做出结论,但是我所拿走的东西就是要使用auto
, 除非 :
- 它伤害可读性
- 有关于自动types转换(例如从构造函数,赋值,模板中间types,整数宽度之间的隐式转换)
explicit
自由使用有助于减less对后者的关注,这有助于最大限度地减less前者是一个问题的时间。
改写Herb所说的话:“如果你不做X,Y和Z,那么使用auto
,学习X,Y和Z是什么,然后去其他地方使用auto
。
auto
可能是非常危险的与expression式模板结合在一起被线性代数库如Eigen或OpenCV广泛使用。
auto A = Matrix(...); auto B = Matrix(...); auto C = A * B; // C is not a matrix. It is a matrix EXPRESSION. cout << C; // The expression is evaluated and gives the expected result. ... // <code modifying A or B> cout << C; // The expression is evaluated AGAIN and gives a DIFFERENT result.
由这种types的错误引起的错误是debugging的一大痛苦。 一种可能的补救措施是将结果明确地转换为预期的types,如果你对从左到右的声明风格使用auto的话。
auto C = Matrix(A * B); // The expression is now evaluated immediately.
我用auto
wihout限制,并没有面临任何问题。 我甚至有时最终将它用于像int
这样的简单types。 这使得c ++成为更高层次的语言,并且允许像在Python中一样在c ++中声明variables。 在编写python代码之后,我甚至有时会编写例如
auto i = MyClass();
代替
MyClass i;
这是一个我会说这是滥用auto
关键字的情况。
通常我不介意什么是对象的确切types,我对它的function更感兴趣,而且函数名称通常会说明它们返回的对象, auto
不会伤害它:例如auto s = mycollection.size()
,我可以猜测s
是一种整数,而在我关心确切types的罕见情况下,让我们来检查函数原型(我的意思是,我更喜欢检查什么时候需要信息,而不是在编写代码时的先验,以防万一有一天它会被使用,例如int_type s = mycollection.size()
)。
关于这个从被接受的答案中得到的例子:
for ( auto x = max_size; x > 0; --x )
在我的代码中,我仍然在这种情况下使用auto
,如果我想要x
是无符号的,那么我使用了一个名为make_unsigned
的实用函数,它清楚地expression了我的担忧:
for ( auto x = make_unsigned(max_size); x > 0; --x )
免责声明:我只是描述我的使用,我没有资格提供build议!
我注意到的一个危险就是参考。 例如
MyBigObject& ref_to_big_object= big_object; auto another_ref = ref_to_big_object; // ?
问题是another_ref实际上不是一个引用,在这种情况下它是MyBigObject而不是MyBigObject&。 你最终没有意识到复制一个大对象。
如果你直接从一个方法获得一个引用,你可能不会考虑它的实际内容。
auto another_ref = function_returning_ref_to_big_object();
你会需要“自动&”或“常量自动&”
MyBigObject& ref_to_big_object= big_object; auto& another_ref = ref_to_big_object; const auto& yet_another_ref = function_returning_ref_to_big_object();
C ++程序的一个主要问题是它允许你使用未初始化的variables 。 这导致我们讨厌非确定性的程序行为。 应该指出,现代编译器现在抛出适当的/消息的警告消息,如果程序轮胎使用它。
只是为了说明这一点,考虑下面的c + +程序:
int main() { int x; int y = 0; y += x; }
如果我使用现代编译器(GCC)编译这个程序,它会给出警告。 如果我们正在使用真正复杂的生产代码,这样的警告可能不是很明显。
main.cpp:在函数'int main()'中:
main.cpp:4:8: 警告 :在此函数中未初始化使用“x”[-Winitinitialized]
y + = x;
^
================================================== ===============================现在,如果我们改变我们的程序使用自动 ,然后编译我们得到以下内容:
int main() { auto x; auto y = 0; y += x; }
main.cpp:在函数'int main()'中:
main.cpp:2:10: 错误 :'auto x'的声明没有初始值设定项
auto x; ^
使用auto时,不可能使用未初始化的variables。 这是我们可能获得(免费),如果我们开始使用汽车的主要优势。
这个概念和其他伟大的现代C ++概念在CppCon14谈话中由C ++专家Herb Shutter解释:
回到基础! 现代C ++风格的要点
使用auto
,它是有道理的推断types。 如果你有一个你知道的是一个整数,或者你知道它是一个string,只要使用int / std :: string等。我不会担心语言function的“过度使用”,除非它达到了荒谬的地步,或混淆代码。
无论如何,这是我的意见。
auto
关键字只能用于局部variables,而不能用于参数或类/结构成员。 所以,在任何你喜欢的地方使用它们是安全和可行的。 我确实使用了很多。 types是在编译时推导出来的,debugging器显示debugging时的types, sizeof
报告正确, decltype
会给出正确的types – 没有任何伤害。 我不认为auto
被滥用,永远!
TL; DR:参见底部的经验法则。
接受的答案build议以下经验法则:
使用
auto
当很难说如何写一种types的一见钟情,但expression的右侧的types是显而易见的。
但我会说这太严格了。 有时候我不关心types,因为这个语句足够丰富,而且不费吹灰之力就可以抽出时间。 这是什么意思? 考虑一些答案中popup的例子:
auto x = foo();
是什么让这个滥用auto
的例子? 难道我不知道foo()
的返回types是什么? 那么,如果我清楚地知道,那会更好,但这不是我主要关心的问题。 我会说, x
是一个(几乎)无意义的variables名,而foo()
的含义还不够清楚。 至less为了这个例子,让我们说这不是。 如果我们拥有完全相同types的东西,
auto result = frobnicate_the_bar();
那么我通常不关心函数的返回types是否明显。 阅读这个陈述,我知道自己在做什么,而且我对于返回值的语义知之甚less,并不需要知道它的types。
所以我的答案是:编译器允许时使用auto
,除非:
- 你觉得variables名和初始化/赋值expression式不能提供有关语句正在做什么的足够的信息。
- 你觉得variables名和初始化/赋值expression式一起提供了关于types应该是什么types的“误导性”的信息 – 也就是说,如果你必须猜测什么是auto而不是auto,那么你就可以猜测 – 这将是错误的,这个错误的假设在代码后面有影响。
- 你想强制一个不同的types(例如一个参考)。
并且:
- 在将
auto
replace为具体types之前,首先给出一个有意义的名称(当然不包含types名称)。
我对auto
驾驶的一个苦恼就是用lambdaexpression式来使用它 :
auto i = []() { return 0; }; cout<<"i = "<<i<<endl; // output: 1 !!!
其实,这里i
解决了函数int(*)()
指针。 这只是一个简单的cout
,但是想象一下在template
使用它会导致什么样的错误编译/运行时错误。
你应该避免使用这样的expression式auto
,并把适当的return
types(或控制的decltype()
)
以上例子的正确用法是,
auto i = []() { return 0; }(); // and now i contains the result of calling the lambda