代码之家  ›  专栏  ›  技术社区  ›  Farsan Rashid

为什么我需要显式地写“auto”关键字?

  •  77
  • Farsan Rashid  · 技术社区  · 6 年前

    我正在从C++ 98向C++ 11移动,并且已经熟悉了 auto 关键字。我想知道为什么我们需要明确声明 汽车 如果编译器能够自动推断类型。我知道C++是一种强类型的语言,这是一条规则,但是不显式地声明一个变量是不可能达到同样的结果的。 汽车 ?

    7 回复  |  直到 6 年前
        1
  •  154
  •   Bathsheba    6 年前

    删除显式 auto 会破坏语言:

    例如

    int main()
    {
        int n;
        {
            auto n = 0; // this shadows the outer n.
        }
    }
    

    在那里你可以看到 汽车 不会 阴影 外部 n .

        2
  •  39
  •   Aconcagua    6 年前

    你的问题有两种解释:

    • 为什么我们需要“自动”呢?我们就不能简单地放下它吗?
    • 为什么我们必须使用汽车?如果没有给出,我们就不能把它隐在心里吗?

    芭丝谢芭 answered 很好地,第一种解释,第二种解释,考虑以下内容(假设到目前为止还没有其他声明; 假设 有效C++:

    int f();
    double g();
    
    n = f(); // declares a new variable, type is int;
    d = g(); // another new variable, type is double
    
    if(n == d)
    {
        n = 7; // reassigns n
        auto d = 2.0; // new d, shadowing the outer one
    }
    

    有可能的话,其他语言也能很好地处理(好吧,也许除了阴影问题)。然而,C++中不是这样的,现在的问题(在第二种解释的意义上)是: 为什么?

    这一次,答案并不像第一次解释那样明显。不过,有一点很明显:对关键字的明确要求使语言更安全(我不知道这是否是促使语言委员会做出决定的原因,但它仍然是一个要点):

    grummel = f();
    
    // ...
    
    if(true)
    {
        brummel = f();
      //^ uh, oh, a typo...
    }
    

    我们可以同意这一点,不需要任何进一步的解释吗?

    然而,不需要auto的更大危险在于,它意味着在远离函数的地方添加一个全局变量(例如在头文件中)可以将该函数中本地范围变量的声明转换为对全局变量的赋值。e.可能造成灾难性(当然非常混乱)后果。

    (引) psmears' 评论,因为它的重要性-感谢暗示)

        3
  •  14
  •   Nicol Bolas    6 年前

    如果不显式声明变量,是否不可能实现相同的结果? auto ?

    我将以一种有助于你理解为什么你需要的方式对你的问题稍加修改。 汽车 :

    如果没有明确的 使用类型占位符 ?

    不是吗? 可能的 ?当然,这是“可能的”。问题是这样做是否值得。

    在其他语言中,大多数不使用类型名的语法有两种工作方式。有一种走的方式,在哪里 name := value; 声明变量。还有一种类似巨蟒的方式, name = value; 声明新变量if name 以前未声明。

    假设在C++中没有语法应用问题(尽管我已经看到了 identifier 然后 : 在C++中意味着“制作标签”。那么,与占位符相比,你会失去什么呢?

    好吧,我不能再这样做了:

    auto &name = get<0>(some_tuple);
    

    看, 汽车 总是意味着“价值”。如果要获取引用,则需要显式使用 & . 如果赋值表达式是一个prvalue,那么它将无法编译。基于赋值的语法都不能区分引用和值。

    现在,如果给定的值是一个引用,那么可以使用这种赋值语法来推导引用。但这意味着你不能:

    auto name = get<0>(some_tuple);
    

    这个 副本 从元组创建独立于 some_tuple . 有时候,这正是你想要的。如果您想从元组中移动 auto name = get<0>(std::move(some_tuple)); .

    好吧,也许我们可以稍微扩展一下这些语法来解释这种区别。也许吧 &name := value; &name = value; 意思是推断一个参考 auto& .

    好的,很好。这个怎么样:

    decltype(auto) name = some_thing();
    

    哦,没错,C++实际上有 two placeholders: auto and decltype(auto) . 这个推论的基本思想是,它的工作原理与你所做的完全一样。 decltype(expr) name = expr; . 所以在我们的情况下,如果 some_thing() 是一个物体,它会推断出一个物体。如果 一些事情 是一个引用,它将推导一个引用。

    当您在模板代码中工作并且不确定函数的返回值是什么时,这非常有用。这对于转发是很好的,而且它是一个必不可少的工具,即使它没有被广泛使用。

    所以现在我们需要在语法中添加更多内容。 name ::= value; 意思是“做什么” decltype(自动) 是的”。我没有与蟒蛇变种相当的东西。

    看看这个语法,这不是很容易意外地错误输入吗?不仅如此,它还很难自我记录。即使你从未见过 decltype(自动) 以前,它很大而且很明显,你至少可以很容易地分辨出发生了什么特别的事情。而视觉上 ::= := 是最小的。

    但这是意见方面的东西,还有更多实质性的问题。请参见,所有这些都基于使用赋值语法。好。。。你住的地方怎么样 不能 使用赋值语法?这样地:

    for(auto &x : container)
    

    我们把那个换成 for(&x := container) ?因为这似乎在说些什么 非常不同 基于范围 for . 它看起来像是来自正则的初始值设定项语句 对于 循环,而不是基于范围的 对于 . 它也将是一种不同于非演绎案例的语法。

    另外,复制初始化(使用 = 在C++中与直接初始化不一样(使用构造函数语法)。所以 名称:=值; 在以下情况下可能不工作 auto name(value) 会的。

    当然,你可以声明 = 将使用直接初始化,但这将与C++其余部分的行为方式完全一致。

    另外,还有一件事:C++ 14。它给了我们一个有用的演绎特征:回归式演绎。但这是基于占位符的。很像基于距离的 对于 它基本上基于编译器填充的类型名,而不是应用于特定名称和表达式的某些语法。

    看,所有这些问题都来自同一个来源:您发明了用于声明变量的全新语法。基于占位符的声明不必发明新的 句法 . 他们使用的语法与以前完全相同;他们只是使用一个新的关键字,它的作用类似于一个类型,但有特殊的含义。这使得它可以在基于范围的环境中工作。 对于 以及用于退货类型扣除。它允许它有多种形式( 汽车 VS decltype(自动) )等等。

    占位符之所以起作用,是因为它们是解决问题的最简单的方法,同时保留了使用实际类型名的所有好处和通用性。如果你想出了另一个像占位符一样普遍工作的替代方案,那么它不太可能像占位符那样简单。

    除非只是用不同的关键字或符号拼写占位符…

        4
  •  12
  •   Andrew Svietlichnyy    6 年前

    简而言之: auto 在某些情况下可以删除,但这会导致不一致。

    首先,如前所述,C++中的声明语法是 <type> <varname> . 显式声明需要某种类型或至少一个声明关键字来代替它。所以我们可以用 var <varname> declare <varname> 或者别的什么,但是 汽车 是C++中的一个很长的关键字,是自动类型推导关键字的一个很好的候选对象。

    是否可以通过赋值隐式声明变量而不破坏所有内容?

    有时候是的。不能在函数之外执行赋值,因此可以在函数声明中使用赋值语法。但是这种方法会给语言带来不一致性,可能导致人类的错误。

    a = 0; // Error. Could be parsed as auto declaration instead.
    int main() {
      return 0;
    }
    

    当涉及到任何类型的局部变量时,显式声明是控制变量范围的方法。

    a = 1; // use a variable declared before or outside
    auto b = 2; // declare a variable here
    

    如果允许使用不明确的语法,则声明全局变量可能会突然将局部隐式声明转换为赋值。找到这些转换需要检查 一切 . 为了避免冲突,您将需要所有全局的唯一名称,这会破坏作用域的整个概念。所以很糟糕。

        5
  •  11
  •   rustyx    6 年前

    auto 是一个关键字,可以在通常需要指定 类型 .

      int x = some_function();
    

    可以通过使 int 自动推断的类型:

      auto x = some_function();
    

    所以它是对语言的保守扩展;它符合现有的语法。没有它 x = some_function() 变成赋值语句,不再是声明。

        6
  •  9
  •   code707    6 年前

    语法必须明确并且向后兼容。

    如果删除auto,将无法区分语句和定义。

    auto n = 0; // fine
    n=0; // statememt, n is undefined.
    
        7
  •  3
  •   Bert Bril    6 年前

    加上前面的答案,一个旧屁的额外注释:看起来您可能会认为它是一个优势,可以开始使用一个新的变量,而不必以任何方式声明它。

    在可能隐式定义变量的语言中,这可能是一个大问题,特别是在大型系统中。您输入了一个错别字,调试了几个小时,结果发现无意中引入了一个值为零(或更糟)的变量- blue VS bleu , label VS lable …结果是,如果不彻底检查精确的变量名,就无法真正信任任何代码。

    只是使用 auto 告诉编译器和维护人员声明一个新变量是您的意图。

    想想看,为了避免这种噩梦,“implicit none”语句是在Fortran中引入的——现在你可以看到它被用在所有重要的Fortran程序中。没有它只是…吓人的。