使用 C++11 自动关键字多少钱才算太多

How much is too much with C++11 auto keyword?

本文关键字:太多 多少 关键字 C++11 使用      更新时间:2023-10-16

我一直在使用 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;

在这里,我不在乎hw是浮子还是双打,只在乎它们是适合表达身高和体重的任何类型

或者考虑

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除非

  1. 这损害了可读性
  2. 存在自动类型转换(例如,从构造函数、赋值、模板中间类型、整数宽度之间的隐式转换
  3. (存在问题

自由使用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()的返回类型的无知吗?好吧,如果我知道的话,它确实可能会有所帮助,但是 - 这不是我的主要关注点。更严重的问题是,xf()毫无意义。如果我们有:

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