为什么需要显式编写'auto'关键字?


80

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


请记住,C家族区分大小写。去调试一些JS代码,其中作者省略了“ var”,并使用诸如“ Bob”,“ bob”和“ boB”之类的单独变量。啊。
PTwr

46
即使有可能,这也是一个中等可怕的想法。可以说,Python(和类似语言)的最大弱点是缺少声明语法。简单分配将创建一个新变量是错误的主要来源。
康拉德·鲁道夫

@KonradRudolph:JS的声明语法并没有更好。我认为他们的意思是无法以细粒度的方式限制变量的范围。
user541686

4
@Mehrdad“更改语义”≠“强制性”。问题在于JavaScript确实接受隐式声明。是的,它们在语义上有所不同,但这丝毫没有帮助。
康拉德·鲁道夫'18

1
又见双待“为什么真正的Perl用户使用‘我’关键字stackoverflow.com/questions/8023959/why-use-strict-and-warnings/...
晏TM

Answers:


156

删除显式auto将破坏语言:

例如

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

在那里你可以看到下降auto不会遮住外部n


8
正在输入完全相同的内容。将分配与初始化区分开来,将需要标准方面的任意选择。由于我们已经有了“任何可能成为声明的东西都是声明”的规则,因此我们陷入了非常黑暗的水中。
StoryTeller-Unslander Monica '18

4
这不是问题。像golang一样,您显然可以使用类似的方法n := 0来引入新变量。为什么auto使用基于意见的问题。
llllllllll

23
@liliscent-是否基于意见?(a)它已经是保留关键字。(b)含义很清楚。(c)避免了引入新令牌的需求(例如:=)。(d)它已经符合语法要求。我认为这里的意见空间很小。
StoryTeller-Unslander Monica,

2
@StoryTeller如果x = f()声明新变量(如果尚不存在),则不需要新标记,从而获得f的返回值类型...需要auto来显式声明变量,但是,这降低了声明新变量的风险偶然地(例如由于打字错误...)。
阿空加瓜

34
@Aconcagua- “声明一个新变量(如果尚不存在)”但是阴影该语言的一部分,并且仍然必须起作用,如Bathsheba所示。这是一个超出人们想象的更大的问题。这与从头开始的语言设计无关,而与改变一种生动的呼吸语言有关。很难做。Kinda喜欢在超速驾驶的汽车上改变方向盘。
StoryTeller-Unslander Monica

40

您的问题有两种解释:

  • 为什么我们根本需要“自动”?我们不能简单地放弃它吗?
  • 为什么我们必须使用自动?如果没有给出,我们就不能隐含它吗?

Bathsheba很好地回答了第一种解释,对于第二种解释,请考虑以下内容(假设到目前为止没有其他声明;假设是有效的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...
}

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

但是,不需要自动的更大危险是,这意味着在远离函数的位置(例如,在头文件中)添加全局变量可能会使原本打算在本地声明-该函数中的作用域变量转换为全局变量的赋值……可能会带来灾难性(当然也很令人困惑)的后果。

(由于其重要性,引用了psmears的评论-感谢您的暗示)


24
auto我认为,不要求这样做的更大危险是,这意味着在远离函数的位置(例如,在头文件中)添加全局变量可能会使原本打算在本地范围内声明的内容变成事实。该函数中的变量转换为对全局变量的赋值……可能会带来灾难性(当然也很令人困惑)的后果。
psmears '18

1
@psmears像Python这样的语言通过要求将变量明确指定为全局/非本地变量来避免这种情况;默认情况下,它只是使用该名称创建一个新的本地变量。(当然,您无需global <variable>声明就可以从全局变量中读取内容。)当然,这将需要对C ++语言进行更多修改,因此可能不可行。
JAB

@JAB-是的,我知道这一点……我没有提到它,因为正如您所说,它需要对语言进行更多的修改:)
psmears

FWIW,推动语言委员会发展的最有可能是历史。AFAIK,最初编写C时,局部变量保存在堆栈中,并且C要求首先在块中显式声明所有变量。这样一来,编译器可以在编译其余代码之前确定该块的存储要求,并允许其发出正确的指令序列以在堆栈上分配空间。MOV R6 R5 SUB #nnn R6PDP-11上的IIRC假定R5被用作帧指针,R6是堆栈指针。nnn是所需的存储字节数。
dgnuff

2
人们确实设法使用Python,它每次在作业的左侧出现新名称时都会高兴地声明一个变量(即使该名称是一个错字)。但是我确实认为这是该语言最严重的缺陷之一。
霍布斯(Hobbs)

15

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

我将以某种方式稍微改一下您的问题,以帮助您了解为什么需要auto

如果不显式使用类型占位符,是否不可能达到相同的结果?

是不是没有可能?当然,这是“可能的”。问题是这样做是否值得付出努力。

其他没有类型名称的语言中的大多数语法都以两种方式之一工作。有一种类似Go的方式,其中name := value;声明了一个变量。还有一种类似于Python的方式,name = value;如果name以前没有声明过,则在其中声明一个新变量。

让我们假设将任何一种语法应用于C ++都没有语法问题(即使我已经看到C ++中identifier紧随其后的:含义是“制作标签”)。那么,与占位符相比,您失去了什么?

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

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

看,auto总是意味着“价值”。如果要获取参考,则需要显式使用&。如果赋值表达式为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 ++实际上有两个占位符:autodecltype(auto)。这种推论的基本思想是,它就像您已经完成的一样工作decltype(expr) name = expr;。因此,在我们的例子中,如果some_thing()是一个对象,它将推论出一个对象。如果some_thing()是参考,它将推断出参考。

当您使用模板代码并且不确定确切函数的返回值时,这非常有用。这对于转发非常有用,即使它没有被广泛使用,它也是必不可少的工具。

因此,现在我们需要在语法中添加更多内容。name ::= value;意思是“做什么decltype(auto)”。我没有等效的Pythonic版本。

看看这种语法,不是很容易偶然输入错误吗?不仅如此,它几乎没有自我记录。即使您从未见过decltype(auto),它也足够大而且很明显,您至少可以轻松地说出发生了什么特别的事情。而::=和之间的视觉差异:=很小。

但这就是意见。还有更多实质性问题。看,所有这些都是基于使用赋值语法的。好吧...那您不能使用赋值语法的地方呢?像这样:

for(auto &x : container)

我们将其更改为for(&x := container)吗?因为这似乎表明与基于范围的区别很大for。看起来像是来自常规for循环的初始化语句,而不是基于范围的for。它与非推导情况的语法也将不同。

另外,=在C ++中,复制初始化(使用)和直接初始化(使用构造函数语法)不是同一回事。因此,在name := value;可能的情况下可能无法正常工作auto name(value)

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

另外,还有一件事:C ++ 14。它给了我们一个有用的推论功能:返回类型推论。但这是基于占位符的。就像基于范围的一样for,它从根本上基于由编译器填充的类型名,而不是基于应用于特定名称和表达式的某种语法。

瞧,所有这些问题都来自同一个来源:您正在发明全新的语法来声明变量。基于占位符的声明不必发明新的语法。他们使用的语法与以前完全相同。他们只是采用了一种新关键字,该关键字的作用类似于类型,但具有特殊含义。这就是它可以在基于范围for和归纳类型推断中工作的原因。它是什么允许它具有多种形式(autovs. decltype(auto))。依此类推。

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

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


2
恕我直言,这是解决占位符选择背后一些实质性理由的唯一答案。通用lambda中的类型推导可能是另一个示例。遗憾的是,这个答案之所以这么少是因为它发布得太晚了……
llllllllll

@liliscent:“通用lambda的类型推导可能是另一个例子。 ”我没有提及,因为它的语义与auto声明/返回值推导不同。
尼科尔·波拉斯

@liliscent:的确,这个答案迟到了。向上。(不过,其中的一项改进是提到了C ++的思想,即如果可以将某些内容声明为声明,则为声明。)
Bathsheba,

12

简而言之:auto在某些情况下可能会被丢弃,但这会导致不一致。

首先,正如所指出的,C ++中的声明语法为<type> <varname>。显式声明在其位置需要某种类型或至少一个声明关键字。因此,我们可以使用var <varname>ordeclare <varname>或诸如此类的东西,但是auto它在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

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


11

auto是一个关键字,可在通常需要指定类型的地方使用。

  int x = some_function();

可以通过int自动推导类型来使其更通用:

  auto x = some_function();

因此,这是对该语言的保守扩展;它适合现有语法。没有它,它将x = some_function()成为赋值语句,不再是声明。


9

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

如果删除了auto,将无法在语句和定义之间进行区分。

auto n = 0; // fine
n=0; // statememt, n is undefined.

3
重要的一点是,auto它已经是一个关键字(但具有过时的含义),因此它不会破坏使用它作为名称的代码。这是没有选择更好的关键字(例如var或)的原因let
FRAX

1
@Frax IMOauto实际上是一个非常出色的关键字:它准确表达了它的意思,即用“自动类型”替换类型名称。因此,使用诸如varorlet的关键字,即使明确指定了类型(即var int n = 0或之类的),也应要求使用关键字var n:Int = 0。基本上,这就是在Rust中完成的方式。
大约

1
@leftaroundabout虽然auto在现有语法的上下文中绝对是出色的,但我想说的是,如果考虑到历史内容之外,像这样var int x = 42的基本变量定义(带有var x = 42int x = 42作为速记)将比当前语法更有意义。但这主要是口味问题。但是,您是对的,我应该在原始评论中写“原因之一”而不是“原因” :)
Frax

@leftaroundabout:“ auto实际上是一个非常出色的关键字:它确切表达了它的意思,即它用'automatic type'代替了类型名称。”事实并非如此。没有“自动类型”。
Lightness Races in Orbit

在您可以使用的任何给定上下文中,@LightnessRacesinOrbitauto都有一个自动类型(一个不同的类型,具体取决于表达式)。
大约

3

除了以前的答案外,还有一个旧屁的注释:看起来您可能会发现,可以直接使用新变量而无需以任何方式声明它是一种优势。

在可能隐式定义变量的语言中,这可能是个大问题,尤其是在较大的系统中。您打了一个错字,调试了好几个小时,才发现您无意中引入了一个值为零(或更糟)的变量blue-vs bleulabelvs lable...结果是,如果不仔细检查精确度,您将无法真正信任任何代码变量名。

仅使用即可auto告诉编译器和维护者您打算声明一个新变量。

考虑一下,为了避免这种噩梦,FORTRAN中引入了“隐式无”语句-并且您会发现它已用于当今的所有认真的FORTRAN程序中。没有它简直是……令人恐惧。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.