究竟什么是C ++中的“副作用”?

时间:2012-03-05 08:27:39

标签: c++ c++11

它是一个定义明确的标准术语,还是开发人员用来解释概念(......和概念是什么)的术语?据我所知,这与令人困惑的sequence points有关,但我不确定。

我找到了一个定义here,但这不会使每个代码语句产生副作用吗?

  

副作用是运算符,表达式,语句或函数的结果,即使在完成对运算符,表达式,语句或函数的评估之后,它仍然存在。

有人可以解释一下“副作用”一词在C ++中的正式含义,它的意义是什么?

作为参考,有些问题涉及副作用:

  1. Is comma operator free from side effect?
  2. Force compiler to not optimize side-effect-less statements
  3. Side effects when passing objects to function in C++

4 个答案:

答案 0 :(得分:17)

“副作用”由[intro.execution]中的C ++标准定义:

  

读取由volatile glvalue(3.10)指定的对象,修改对象,调用库I / O函数或调用执行任何这些操作的函数都是副作用,这些都是状态的变化。执行环境。

答案 1 :(得分:9)

  

C ++中的'副作用'究竟是什么?这是一个明确定义的标准术语......

c ++ 11 draft - 1.9.12:访问由volatile glvalue(3.10)指定的对象,修改对象,调用库I / O函数或调用执行任何这些操作的函数都是效果,它们是执行环境状态的变化。表达式(或子表达式)的评估通常包括值计算(包括确定用于glvalue评估的对象的身份以及获取先前分配给用于prvalue评估的对象的值)和启动副作用。当对库I / O函数的调用返回或对volatile对象的访问进行评估时,即使调用隐含的某些外部操作(例如I / O本身)或易失性访问,也会认为副作用已完成可能尚未完成。

  

我在这里找到了一个定义,但这不会使每个代码语句产生副作用吗?

     

副作用是运算符,表达式,语句或函数的结果,即使在完成对运算符,表达式,语句或函数的评估之后,它仍然存在。

     

有人可以解释一下“副作用”一词在C ++中的正式含义,它的意义是什么?

重要的是,在评估表达式时,它们可以修改程序状态和/或执行I / O.在C ++的无数地方允许使用表达式:变量赋值,if / else / while条件,for循环设置/测试/修改步骤,函数参数等...几个例子:++xstrcat(buffer, "append this")

在C ++程序中,标准授予优化器生成代表程序操作的代码的权限,但要求与序列点之前的步骤相关的所有操作出现在与序列点之后的步骤相关的任何操作之前。

C ++程序员倾向于关心序列点和副作用的原因是没有你想象的那么多的序列点。例如:给定x = 1; f(++x, ++x);,您可能希望调用f(2, 3),但实际上是未定义的行为。此行为未定义,因此编译器的优化器可以更自由地安排具有副作用的操作,以尽可能最有效的顺序运行 - 甚至可以并行运行。它还避免了编译器编写者检测到这种情况的负担。

  

1.逗号操作员是否没有副作用?

是 - 逗号运算符引入了一个序列点:左侧的步骤必须在右侧执行之前完成。在http://en.wikipedia.org/wiki/Sequence_point有一个序列点列表 - 你应该读这个! (如果你不得不询问副作用,那么在解释这个答案时要小心 - 在函数参数,数组初始化元素等之间不调用“逗号运算符”。逗号运算符相对较少使用且有些模糊。做一些阅读如果你不确定逗号运算符到底是什么。)

  

2.强制编译器不优化无副作用的语句

我认为你的意思是“副作用”。编译器没有义务支持任何此类选项。如果他们尝试过,他们会表现出什 - 标准没有规定在这种情况下应该做什么。有时候大多数程序员可能会有一种直观的期望,但有时甚至是真正的仲裁。

  

3.将对象传递给C ++函数时的副作用

调用函数时,必须在函数调用发生之前完全评估所有参数 - 并触发其副作用。但是,编译器对于在任何其他参数表达式之前评估特定参数表达式没有任何限制。它们可以重叠,并行等等。因此,在f(expr1, expr2)中 - 评估expr2的一些步骤可能在expr1之前运行,但expr1可能仍然先完成 - 它是 undefined

答案 2 :(得分:6)

“副作用”一词源于命令式语言和纯函数式语言之间的区别。 C ++表达式可以做三件事:

  1. 计算结果(或在void表达式的情况下计算“无结果”),
  2. 引发异常,而不是评估结果,
  3. 除了1或2之外,否则改变程序名义上运行的抽象机器的状态。
  4. (3)是副作用,“主效应”是评价表达的结果。例外是一个稍微尴尬的特殊情况,因为改变控制流确实改变了抽象机器的状态(通过改变当前的执行点),但不是副作用。当然,构造,处理和销毁异常的代码可能有其自身的副作用。

    相同的原则适用于函数,返回值代替表达式的结果。

    因此,int foo(int a, int b) { return a + b; }只计算一个返回值,它不会改变其他任何东西。因此它没有副作用,当涉及到程序的推理时(例如证明它是正确的,或者在编译时优化),它有时是函数的一个有趣属性。 int bar(int &a, int &b) { return ++a + b; }确实有副作用,因为修改调用者的对象a是函数的另一个影响,而不仅仅是计算返回值。它不允许使用纯函数式语言。

    关于“已完成评估”的引用中的内容是指表达式(或函数的返回值)的结果可以是“临时对象”,在完整结束时销毁它发生的表达。因此,创建临时性不是该定义的“副作用”:其他变化是。

答案 3 :(得分:0)

1.9.6

  

抽象机器的可观察行为是它的序列   读取和写入volatile数据并调用库I / O.   功能

副作用是影响可观察行为的任何因素。

请注意,标准指定了异常,其中可观察行为不必与抽象机器的行为一致 - 请参阅返回值优化,临时副本省略。