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

时间:2016-01-13 03:44:36

标签: c++ c++11 type-inference auto

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

使用auto声明变量而不是明确声明变量是否有缺点?

14 个答案:

答案 0 :(得分:109)

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

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

给出类似

的声明
auto result = CallSomeFunction(x,y,z);

您不一定了解result的类型。它可能是int。它可能是一个指针。它可能是别的东西。所有这些都支持不同的操作。您还可以通过稍微更改(例如

)来显着更改代码
auto result = CallSomeFunction(a,y,z);

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

由于没有明确了解某些变量的类型,因此更难以严格证明代码按预期工作的说法。这意味着需要更多的努力来证明“适合目的”的主张。在高度关键性(例如安全关键或任务关键型)领域。

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

答案 1 :(得分:75)

这不是auto完全有原则性的缺点,但实际上对某些人来说似乎是一个问题。基本上,有些人要么:a)将auto视为类型的救世主,并在使用它时关闭他们的大脑,或者b)忘记auto总是推断出值类型。这导致人们做这样的事情:

auto x = my_obj.method_that_returns_reference();

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

const auto& stuff = *func_that_returns_unique_ptr();

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

我认为,只要时间人们会调整,并理解分工:auto推断出基本类型,但你仍然想要考虑引用和常量。但这需要一些时间。

答案 2 :(得分: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);

逻辑上,我们希望SomeTypeVector,我们绝对希望在我们的代码中将其视为此类。但是,出于优化目的,我们可以使用实现表达式模板的代数库,实际的返回类型是:

MultExpression<Matrix, Vector> operator* (const Matrix &lhs, const Vector &rhs);

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

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

void compute()
{
  Vector res = a * (b * (c * v));
  // do something with res
}

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

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

答案 3 :(得分:13)

缺点之一是,有时您无法使用class Group < ActiveRecord::Base has_many :projects has_many :users ... 声明resources :projects do resources :experiments do ... end end 。在这个从this question获取的代码示例中,您将获得普通(非常量)迭代器:

const_iterator

答案 4 :(得分:11)

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

auto output = doSomethingWithData(variables);

现在,要确定输出的类型,您必须追踪doSomethingWithData函数的签名。

答案 5 :(得分:9)

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

我({强})认为auto是帮助您编写通用代码,不是为了减少输入。 C ++是一种语言,其目标是让您编写健壮的代码,而不是以最大限度地缩短开发时间 从C ++的许多功能来看,这是相当明显的,但不幸的是,一些像auto这样的新版本减少了打字,误导了人们认为他们应该开始懒得打字。

auto天之前,人们使用typedef s,这很棒,因为typedef允许图书馆的设计师帮助您弄清楚返回类型应该是什么,所以他们的图书馆按预期工作。当您使用auto时,从该类的设计器中删除该控件,而是要求编译器确定该类型应该是什么,它从工具箱中删除了一个功能最强大的C ++工具,并且存在破坏代码的风险。

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

不幸的是,这些错误在这里的简短示例中难以说明,因为它们的简洁性使得它们不如用户项目中出现的实际示例更具说服力 - 但是,它们很容易在模板中出现 - 需要进行某些隐式转换的重要代码。

如果你想要一个例子,那就有一个here。但需要注意的是:在尝试跳跃并批评代码之前:请记住,围绕这种隐式转换开发了许多知名且成熟的库,并且他们在那里因为他们解决问题< / strong>如果不是不可能的话,可能很难来解决。在批评之前,尝试找出更好的解决方案

答案 6 :(得分:6)

auto没有本身的缺点,我主张(手动)在新代码中随处使用它。它允许您的代码始终进行类型检查,并始终避免静默切片。 (如果B派生自A,并且返回A的函数突然返回B,则auto会按预期运行以存储其返回值)

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

答案 7 :(得分:4)

关键字auto只是从返回值中推断出类型。因此,它不等同于Python对象,例如

# Python
a
a = 10       # OK
a = "10"     # OK
a = ClassA() # OK

// C++
auto a;      // Unable to deduce variable a
auto a = 10; // OK
a = "10";    // Value of const char* can't be assigned to int
a = ClassA{} // Value of ClassA can't be assigned to int
a = 10.0;    // OK, implicit casting warning

由于在编译期间推导出auto,因此在运行时它不会有任何缺点。

答案 8 :(得分:4)

到目前为止,这里没有人提到过,但如果你问我的话,本身就值得回答。

因为(即使每个人都应该知道{C}编写的C != C++代码可以很容易地设计为C ++代码提供基础,因此设计没有太多努力与C ++兼容,这可能是一个设计要求。

我知道一些规则,C中某些定义明确的结构对C++无效,反之亦然。但这只会导致破坏的可执行文件和已知的UB子句适用,大多数时候会被奇怪的循环注意到,导致崩溃或其他任何事情(甚至可能保持未检测到,但这并不重要)。

auto是第一次 1 这种变化!

想象一下,您之前使用auto作为存储类说明符并传输代码。它甚至不一定(取决于它的使用方式)“休息”;它实际上可以默默地改变程序的行为。

这是我应该记住的事情。

1 至少我第一次知道。

答案 9 :(得分:2)

正如我在this answer DescribeImage中所描述的那样,有时可能会导致您没有意图的时髦情境。 你必须明确地说auto有一个引用类型,而只做auto&可以创建一个指针类型。这可能会导致混淆,省略所有说明符,导致引用的副本而不是实际引用。

答案 10 :(得分:2)

我能想到的一个原因是你失去了强制退回课程的机会。如果你的函数或方法返回了长64位,并且你只想要一个32 unsigned int,那么你就失去了控制它的机会。

答案 11 :(得分:1)

另一个令人恼火的例子:

for (auto i = 0; i < s.size(); ++i)

生成警告(comparison between signed and unsigned integer expressions [-Wsign-compare]),因为i是带符号的int。为避免这种情况,您需要编写例如。

for (auto i = 0U; i < s.size(); ++i)

或者更好:

for (auto i = 0ULL; i < s.size(); ++i)

答案 12 :(得分:1)

我认为auto在本地化环境中使用时很好,读者可以轻松地使用它。显然可以扣除它的类型,或者用其类型的注释或推断实际类型的名称记录。那些不了解它如何工作的人可能会以错误的方式接受它,比如使用它而不是template或类似的。以下是我认为的一些好的和坏的用例。

void test (const int & a)
{
    // b is not const
    // b is not a reference

    auto b = a;

    // b type is decided by the compiler based on value of a
    // a is int
}

好用途

迭代

std::vector<boost::tuple<ClassWithLongName1,std::vector<ClassWithLongName2>,int> v();

..

std::vector<boost::tuple<ClassWithLongName1,std::vector<ClassWithLongName2>,int>::iterator it = v.begin();

// VS

auto vi = v.begin();

功能指针

int test (ClassWithLongName1 a, ClassWithLongName2 b, int c)
{
    ..
}

..

int (*fp)(ClassWithLongName1, ClassWithLongName2, int) = test;

// VS

auto *f = test;

糟糕的用途

数据流

auto input = "";

..

auto output = test(input);

功能签名

auto test (auto a, auto b, auto c)
{
    ..
}

琐碎案件

for(auto i = 0; i < 100; i++)
{
    ..
}

答案 13 :(得分:0)

我很惊讶没有人提及此事,但是假设您正在计算某项的阶乘:

#include <iostream>
using namespace std;

int main() {
    auto n = 40;
    auto factorial = 1;

    for(int i = 1; i <=n; ++i)
    {
        factorial *= i;
    }

    cout << "Factorial of " << n << " = " << factorial <<endl;   
    cout << "Size of factorial: " << sizeof(factorial) << endl; 
    return 0;
}

此代码将输出以下内容:

Factorial of 40 = 0
Size of factorial: 4

那绝对不是预期的结果。发生这种情况是因为auto将变量阶乘的类型推导为int,因为它被分配给了1