表达式在C ++中没有副作用

时间:2010-11-10 10:41:08

标签: c++ c++11

看,我不知道的是,为什么像以下这样的程序是合法的?

int main()
{ 
    static const int i = 0;
    i < i > i;
}

我的意思是,当然,没有人实际上有任何当前的程序,其中包含没有副作用的表达式,因为这将是非常无意义的,并且它将使得解析&amp;编译语言更容易。那么为什么不禁止它们呢?语言实际上从允许这种语法中获得了什么好处?

另一个例子是这样的:

int main() {
    static const int i = 0;
    int x = (i);
}

此类陈述的实际好处是什么?

最令人烦恼的解析之类的东西。是否有人在其他功能中声明功能?我的意思是,我们摆脱了隐式函数声明之类的东西,以及类似的东西。为什么不为C ++ 0x摆脱它们?

7 个答案:

答案 0 :(得分:8)

可能因为禁止会使规范变得更复杂,这会使编译器变得更加复杂。

答案 1 :(得分:4)

  

它会解析&amp;编译   语言更容易

我不知道怎么做。如果您需要发出诊断信息,为什么解析和编译i < i > i比解析它更容易,如果你被允许做任何你该死的事情请解析它发出的代码没有副作用?

Java编译器禁止无法访问的代码(而不是无效的代码),这对于程序员来说是一个混合的祝福,并且需要编译器的一些额外工作,而不是C ++编译器实际需要做的事情(基本块依赖性分析)。 C ++应该禁止无法访问的代码吗?可能不是。尽管C ++编译器确实做了足够的优化来识别无法访问的基本块,但在某些情况下它们可能会做得太多。如果if (foo) { ...}是错误的编译时常量,foo应该是非法无法访问的块吗?如果它不是编译时常量,但优化器已经弄清楚如何计算该值,如果它是合法的并且编译器必须意识到它删除它的原因是特定于实现的,以免出错?更特殊的情况。

  实际上没有人有任何现状   表达式为no的程序   其副作用

载荷。例如,如果NDEBUG为true,则assert会扩展为void表达式而不起作用。因此,编译器需要更多特殊情况来允许某些无用的表达式,而不允许其他表达式。

我认为,基本原理是,如果它扩展到无,那么(a)编译器最终会对if (foo) assert(bar);之类的内容发出警告,而(b)这样的代码在发布时是合法的,但不是调试,这只是令人困惑:

assert(foo) // oops, forgot the semi-colon
foo.bar();
  

最令人烦恼的解析之类的东西

这就是为什么它被称为“烦恼”。这真的是一个向后兼容的问题。如果C ++现在改变了那些烦恼的解析的含义,那么现有代码的含义就会改变。正如您所指出的那样,现有的代码并不多,但C ++委员会在向后兼容性方面采取了相当强的路线。如果您想要一种每五分钟更改一次的语言,请使用Perl; - )

无论如何,现在已经太晚了。即使我们有一些很好的洞察力,C ++ 0x委员会已经错过了,为什么某些功能应该被删除或不相容更改,它们不会破坏FCD中的任何内容,除非FCD明确错误。

请注意,对于您的所有建议,任何编译器都可以为它们发出警告(实际上,我不明白您的问题与第二个示例有什么关系,但对于无用的表达式和函数体中的错误解析当然不明白)。如果你没有人故意这么做,警告就不会造成伤害。如果你没有人故意这样做,你所说的移除它们的情况是不正确的。流行编译器中的警告可以为删除功能铺平道路,特别是因为标准主要由编译器编写者编写。我们并不总是对这些事情发出警告这一事实向我表明,它比你想象的更多。

答案 2 :(得分:3)

  • 有时候将无用的语句放入程序并编译它以确保它们是合法的是方便的 - 例如所涉及的类型可以解决/匹配等。

  • 特别是在生成的代码中(宏以及更精细的外部机制,其中策略或类型可能在某些无操作情况下引入无意义扩展的模板),具有较少特殊的不可编译案例以避免事情变得更简单

  • 可能有一些临时注释的代码会删除变量的有意义用法,但是必须同样识别和评论其他地方未使用的所有变量可能会很麻烦。

    < / LI>
  • 虽然在您的示例中,您在无意义的使用之上显示变量“int”,但实际上类型可能要复杂得多(例如operator&lt;())以及操作是否有副作用甚至可能是编译器不知道(例如,外线函数),所以任何好处仅限于更简单的情况。

  • C ++需要一个很好的理由来打破(并保留C)兼容性。

答案 3 :(得分:0)

为什么不采取任何措施作为特例?此外,虽然上述情况很容易被发现,但人们可以想象更复杂的程序,它们不容易识别出没有副作用。

答案 4 :(得分:0)

作为C ++标准的迭代,C ++ 0x必须向后兼容。没有人可以断言你所写的陈述并不存在于由NASA或DoD编写/拥有的某些关键软件中。

无论如何,关于你的第一个例子,解析器不能断言i是一个静态常量表达式,并且i < i > i是一个无用的表达式 - 例如如果i是模板化类型,i < i > i是“无效变量声明”,而不是“无用的计算”,仍然不是解析错误。

答案 5 :(得分:0)

操作员可能超负荷以产生cout<<i;这样的副作用。这就是他们现在无法移除的原因。另一方面,C#禁止将非赋值或方法调用表达式用作语句,我相信这是一件好事,因为它使代码更清晰,语义更正确。然而,C#有机会从一开始就禁止它,而C ++却没有。

答案 6 :(得分:0)

没有副作用的表达式比你在模板化和宏观代码中想象的更频繁。如果您曾声明std::vector<int>,那么您已经实例化了没有副作用的模板代码。如果您为类型std::vector存储了类,T必须在释放自身时销毁其所有元素。在某些时候,这需要类似于ptr->~T();的语句来调用析构函数。 int虽然没有析构函数,但是调用没有副作用,并且将被优化器完全删除。它也可能在循环内,然后整个循环没有副作用,因此整个循环被优化器删除。

因此,如果您禁止没有副作用的表达式,std::vector<int> 将无效

另一个常见案例是assert(a == b)。在发布版本中,您希望这些断言消失 - 但您无法将它们重新定义为空宏,否则像if (x) assert(a == b);这样的语句会突然将下一个语句放入if语句中 - 这是一场灾难!在这种情况下,assert(x)可以重新定义为((void)0),这是一个没有副作用的语句。现在,if语句在发布版本中也能正常工作 - 它什么都不做。

这只是两种常见情况。还有更多你可能不知道的事情。因此,虽然没有副作用的表达似乎是多余的,但它们实际上在功能上非常重要。优化器将完全删除它们,因此也不会对性能产生影响。

相关问题