使用 C++11 自动关键字多少钱才算太多
How much is too much with C++11 auto keyword?
我一直在使用 C++11 标准中可用的新 auto
关键字来处理复杂的模板化类型,我相信它就是为之而设计的。但我也用它来做一些事情,比如:
auto foo = std::make_shared<Foo>();
更怀疑的是:
auto foo = bla(); // where bla() return a shared_ptr<Foo>
我还没有看到太多关于这个话题的讨论。似乎auto
可能被过度使用,因为类型通常是文档和健全性检查的一种形式。您在使用auto
时在哪里划清界限,此新功能的推荐用例是什么?
澄清一下:我不是在征求哲学意见;我要求标准委员会对这个关键字的预期用途,可能会对在实践中如何实现预期用途发表评论。
我认为每当很难说出如何一见钟情地编写类型时,就应该使用 auto
关键字,但表达式右侧的类型是显而易见的。例如,使用:
my_multi_type::nth_index<2>::type::key_type::composite_key_type::
key_extractor_tuple::tail_type::head_type::result_type
获取 boost::multi_index
中的复合键类型,即使您知道它是 int
.你不能只写int
因为它将来可能会改变。在这种情况下,我会写auto
。
因此,如果auto
关键字在特定情况下提高了可读性,请使用它。当读者清楚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
,这样副作用就不那么重要了。除非在明显的情况下,否则您不必担心类型,但它们仍将为您进行静态验证,并且您可以避免一些重复。如果auto
不可行,可以使用decltype
在语义上将类型表示为基于表达式的协定。您的代码看起来会有所不同,但这将是一个积极的变化。
容易。当您不关心类型是什么时,请使用它。例如
for (const auto & i : some_container) {
...
我在这里关心的只是i
是容器中的任何内容。
这有点像typedefs。
typedef float Height;
typedef double Weight;
//....
Height h;
Weight w;
在这里,我不在乎h
和w
是浮子还是双打,只在乎它们是适合表达身高和体重的任何类型。
或者考虑
for (auto i = some_container .begin (); ...
这里我关心的只是一个合适的迭代器,支持operator++()
,在这方面有点像鸭子打字。
此外,lambda的类型无法拼写,因此auto f = []...
是很好的风格。另一种选择是投射到std::function
但这会带来开销。
我真的无法想象auto
的"滥用".我能想象的最接近的是剥夺自己对某个重要类型的显式转换 - 但你不会为此使用auto
,你会构造一个所需类型的对象。
如果可以在不引入副作用的情况下删除代码中的一些冗余,那么这样做一定很好。
反例(借用别人的答案(:
auto i = SomeClass();
for (auto x = make_unsigned (y); ...)
在这里,我们确实关心类型是什么,所以我们应该写Someclass i;
和for(unsigned x = y;...
在 2012 年 C++ 和以后的">问我们任何事情"小组中,Andrei Alexandrescu、Scott Meyers 和 Herb Sutter 之间进行了精彩的交流,讨论了何时使用和不使用auto
。跳到第 25:03 分钟进行 4 分钟讨论。所有三位演讲者都给出了极好的观点,应该记住什么时候不使用auto
.
我强烈鼓励人们得出自己的结论,但我的收获是到处使用auto
,除非:
- 这损害了可读性
- 存在自动类型转换(例如,从构造函数、赋值、模板中间类型、整数宽度之间的隐式转换 (存在问题
自由使用explicit
有助于减少对后者的关注,这有助于最大限度地减少前者成为问题的时间。
改写赫伯所说的话,"如果你不做X,Y和Z,请使用auto
。了解 X、Y 和 Z 是什么,并在其他地方继续使用auto
。
去吧。 在任何地方使用 auto
使编写代码更容易。
任何语言的每个新功能都会被至少某些类型的程序员过度使用。 只有通过一些有经验的程序员(不是菜鸟(适度过度使用,其余有经验的程序员才能学会正确使用的界限。 极端过度使用通常是不好的,但可能是好的,因为这种过度使用可能会导致功能的改进或更好的功能来取代它。
但是,如果我正在处理超过几行的代码,例如
auto foo = bla();
如果类型指示零次,我可能想更改这些行以包含类型。 第一个示例很棒,因为类型只声明一次,auto
使我们不必编写两次混乱的模板化类型。 万岁C++++。 但是,如果类型在附近的行中不容易看到,那么明确显示该类型零次会让我感到紧张,至少在C++及其直接继承者中是这样。 对于其他旨在以更多抽象、多态性和泛型在更高层次上工作的语言,这很好。
是的,它可能被过度使用而损害可读性。我建议在确切类型很长、不可说、对可读性不重要且变量寿命短的上下文中使用它。例如,迭代器类型通常很长且不重要,因此auto
工作:
for(auto i = container.begin(); i != container.end(); ++i);
auto
这里不会影响可读性。
另一个示例是解析器规则类型,它可能很长且复杂。比较:
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;
另一方面,当类型已知且简单时,如果明确声明会好得多:
int i = foo();
而不是
auto i = foo();
auto
与线性代数库(如 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.
由此类错误引起的错误是调试的主要痛苦。一种可能的补救措施是,如果您执意要使用 auto 作为从左到右的声明样式,则将结果显式转换为预期类型。
auto C = Matrix(A * B); // The expression is now evaluated immediately.
我使用了auto
没有限制,没有遇到任何问题。我什至有时最终会将其用于简单的类型,例如 int
.这使得 c++ 对我来说是一种更高级的语言,并允许像在 python 中一样在 c++ 中声明变量。在编写了python代码之后,我甚至有时会编写例如
auto i = MyClass();
而不是
MyClass i;
在这种情况下,我会说这是滥用auto
关键字。
通常我不介意对象的确切类型是什么,我对它的完整性更感兴趣,并且由于函数名称通常会说明它们返回的对象,因此auto
不会受到伤害:例如 auto s = mycollection.size()
,我可以猜测s
将是一种整数,在我关心确切类型的极少数情况下,让我们检查函数原型(我的意思是,我更喜欢在需要信息时检查,而不是在编写代码时先验地检查,以防万一有一天它会有用, 如int_type s = mycollection.size()
(。
关于这个来自接受答案的例子:
for ( auto x = max_size; x > 0; --x )
在我的代码中,在这种情况下我仍然使用 auto
,如果我希望x
是无符号的,那么我使用一个名为 say make_unsigned
的实用程序函数,它清楚地表达了我的担忧:
for ( auto x = make_unsigned(max_size); x > 0; --x )
免责声明:我只是描述我的用途,我没有能力提供建议!
C++该程序的主要问题之一是它允许您使用未初始化的变量。这导致我们出现令人讨厌的非确定性程序行为。 应该注意的是,现代编译器现在会抛出适当的/消息警告消息,如果程序疲惫使用它。
为了说明这一点,请考虑下面的c ++程序:
int main() {
int x;
int y = 0;
y += x;
}
如果我使用现代编译器(GCC(编译此程序,它会发出警告。此类警告可能不是如果我们使用真正复杂的生产代码,则非常明显。
===================================================================================================================================================================================================================================================main.cpp:在函数 'int main((' 中:
main.cpp:4:8:警告:"X"在此函数中使用未初始化 [-文初始化]
y += x;
^
==现在,如果我们更改使用auto的程序,则编译将得到以下内容:
int main() {
auto x;
auto y = 0;
y += x;
}
main.cpp:在函数 'int main((' 中:
主.cpp:2:10:错误:"Auto X"声明没有初始值设定项
auto x; ^
对于 auto,无法使用未初始化的变量。如果我们开始使用 auto,这是我们可能会获得的主要优势(免费(。
这个概念和其他伟大的现代C++概念由C++专家Herb Shutter在他的CppCon14演讲中解释:
返璞归真!现代C++风格精髓
我注意到的一个危险是在参考方面。例如
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();
TL;DR:请参阅底部的经验法则。
接受的答案建议以下经验法则:
当很难说出如何一见钟情地写类型时,请使用
auto
,但表达式右侧的类型是显而易见的。
但我想说这限制太多了。有时我不关心类型,因为该语句信息量足够大,而无需我花时间弄清楚类型。我这是什么意思?考虑一些答案中弹出的示例:
auto x = f();
是什么使这成为滥用auto
的例子?是我对f()
的返回类型的无知吗?好吧,如果我知道的话,它确实可能会有所帮助,但是 - 这不是我的主要关注点。更严重的问题是,x
和f()
毫无意义。如果我们有:
auto nugget = mine_gold();
相反,那么我通常不在乎函数的返回类型是否明显。 阅读该语句,我知道我在做什么,并且我对返回值的语义有足够的了解,以至于不觉得我也需要知道它的类型。
所以我的答案是:只要编译器允许,就使用auto
,除非:
- 您认为变量名与初始化/赋值表达式一起没有提供有关语句正在做什么的足够信息。
- 你觉得变量名和初始化/赋值表达式一起提供了关于类型应该是什么的"误导性"信息 - 即,如果你必须猜测什么而不是自动,你将能够做出猜测 - 这将是错误的,这种错误的假设在代码后面会产生影响。
- 您想强制使用不同的类型(例如引用(。
还有:
- 最好先给出一个有意义的名称(当然不包含类型名称(,然后再将
auto
替换为具体类型。
类型有意义的地方使用auto
。如果你知道它是一个整数,或者你知道它是一个字符串,只需使用 int/std::string 等。我不会担心"过度使用"语言功能,除非它到了荒谬的地步,或者混淆了代码。
无论如何,这是我的意见。
auto
做什么?
它告诉编译器根据变量的初始化值推断(确定(变量的数据类型。它使用类型推导。
应该在哪里使用auto
?
-
当您对了解变量的类型不感兴趣并且只是想使用它。
-
当您想避免令人难以置信的长而丑陋的字体名称时。
-
当你不确定类型时。
-
当您不想在代码中看到未初始化的变量时,即自动强制你初始化一个变量,因此你不能忘记做那。
何时不应使用或auto
缺点
- 参考其功能,auto可能会错误地推断出类型,一个这种情况是
std::vector<bool> vec(10, 0); auto x = vec[2]; bool y = vec[2]; std::cout << typeid(x).name() << "n"; std::cout << typeid(y).name() << "n";
G++ 10.2 的输出令人惊讶:
St14_Bit_reference
b
- 如果你想让你的代码可读和其他人可以理解。它隐藏了数据类型可见性来自读者。
auto
关键字只能用于局部变量,不能用于参数或类/结构成员。因此,在任何地方使用它们是安全且可行的。我确实经常使用它们。类型是在编译时推导的,调试器在调试时显示类型,sizeof
正确报告它,decltype
会给出正确的类型 - 没有伤害。我不认为auto
过度使用,永远!
我对auto
的痛苦经历之一是将其与 lambda 表达式一起使用:
auto i = []() { return 0; };
cout<<"i = "<<i<<endl; // output: 1 !!!
实际上,这里i
解析为int(*)()
的函数指针。这只是一个简单的cout
,但想象一下,当与template
一起使用时,它会导致什么样的不良编译/运行时错误。
您应该避免使用此类表达式auto
,并放置适当的return
类型(或受控decltype()
(
上面示例的正确用法是,
auto i = []() { return 0; }(); // and now i contains the result of calling the lambda
- 在没有太多条件句的情况下,我如何避免被零除
- 错误:字符数组的初始值设定项太多
- 对象实例化调用构造函数的次数太多
- Levenshtein 两个文件的距离花费了太多时间
- 我有三个 getline,但是一旦编译,输入就太多了
- 将使用太多的纹理插值器 - 带旋转的着色器
- C/C++:socket() 创建在循环中失败,打开的文件太多
- 使用Visual Studio在虚幻引擎中创建一个新的类c ++给了我太多的错误
- C++:数组<>初始值设定项太多
- GLib-ERROR:为GWakeup创建管道:打开的文件太多
- C++,从文件读取到结构,然后读取到向量(结构被推入向量太多次,而不仅仅是一次)
- 我会导致太多内存泄漏,以至于我的计算机无响应吗?
- ZeroMq:打开的文件太多.在同一对象上连续增长的fd使用数
- 读取串行命令花费太多时间
- 为什么 Boost unordered_map 在第一次插入时需要太多时间?
- BRK(0) 花费的时间是否太多?
- Valgrind 声称内存释放中的自由空间太多
- 多少听众是太多的观察者模式
- C++异常安全妄想症:多少就是太多
- 使用 C++11 自动关键字多少钱才算太多