将此主题视为以下主题的续集:
上一部分 未定义的行为和序列点
让我们重温一下这个有趣而复杂的表达方式(斜体词组取自上述主题 *smile* ):
i += ++i;
我们说这调用了未定义的行为。我假设当这样说时,我们隐含地假设 i
的 type 是内置类型之一。
如果 i
的 type 是用户定义的类型怎么办?假设它的类型是 Index
,它在本文后面定义(见下文)。它还会调用未定义的行为吗?
如果是,为什么?它不等于写 i.operator+=(i.operator++());
甚至语法上更简单的 i.add(i.inc());
吗?或者,他们是否也调用了未定义的行为?
如果没有,为什么不呢?毕竟,对象 i
在连续序列点之间被修改两次。请记住经验法则:an expression can modify an object's value only once between consecutive "sequence points。如果 i += ++i
是一个表达式,那么它必须调用 undefined-behavior。如果是这样,那么它的等价物 i.operator+=(i.operator++());
和 i.add(i.inc());
也必须调用 undefined-behavior 这似乎是不真实的! (据我所理解)
或者,i += ++i
一开始就不是一个表达式?如果是这样,那么它是什么以及表达式的定义是什么?
如果它是一个表达式,同时它的行为也是明确定义的,那么它意味着与表达式关联的序列点的数量在某种程度上取决于表达式中涉及的操作数的类型。我是否正确(甚至部分正确)?
顺便问一下,这个表情怎么样?
//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!
a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.
您也必须在回复中考虑到这一点(如果您确定知道它的行为)。 :-)
是
++++++i;
在 C++03 中定义良好?毕竟是这个,
((i.operator++()).operator++()).operator++();
class Index
{
int state;
public:
Index(int s) : state(s) {}
Index& operator++()
{
state++;
return *this;
}
Index& operator+=(const Index & index)
{
state+= index.state;
return *this;
}
operator int()
{
return state;
}
Index & add(const Index & index)
{
state += index.state;
return *this;
}
Index & inc()
{
state++;
return *this;
}
};
s
的类型是用户定义的类型!)
它看起来像代码
i.operator+=(i.operator ++());
在序列点方面工作得很好。 C++ ISO 标准的第 1.9.17 节对序列点和函数求值进行了说明:
调用函数时(无论该函数是否内联),在对所有函数参数(如果有)求值之后都有一个序列点,该序列点发生在函数体中的任何表达式或语句执行之前。在复制返回值之后和执行函数外的任何表达式之前还有一个序列点。
例如,这将表明作为 operator +=
的参数的 i.operator ++()
在其评估后具有一个序列点。简而言之,因为重载运算符是函数,所以适用正常的排序规则。
顺便说一句,好问题!我真的很喜欢你强迫我理解一种我已经认为我知道(并且认为我认为我知道)的语言的所有细微差别。 :-)
http://www.eelis.net/C++/analogliterals.xhtml 我想到了模拟文字
unsigned int c = ( o-----o
| !
! !
! !
o-----o ).area;
assert( c == (I-----I) * (I-------I) );
assert( ( o-----o
| !
! !
! !
! !
o-----o ).area == ( o---------o
| !
! !
o---------o ).area );
正如其他人所说,您的 i += ++i
示例适用于用户定义的类型,因为您正在调用函数,并且函数包含序列点。
另一方面,假设 a
是您的基本数组类型,甚至是用户定义的数组类型,a[++i] = i
就没有那么幸运了。您在这里遇到的问题是我们不知道首先评估包含 i
的表达式的哪一部分。可能是 ++i
被评估,传递给 operator[]
(或原始版本)以便在那里检索对象,然后 i
的值被传递给它(在 i
之后递增)。另一方面,可能先评估后边,存储以供以后分配,然后评估 ++i
部分。
++i
和分配的 RHS 上的 i
读数之间都有序列点,那么顺序将是未指定的。因为允许的排序之一在没有干预序列点的情况下完成这两项操作,所以行为是未定义的。
a
和内置的 i
。
我认为它定义明确:
来自 C++ 标准草案 (n1905) §1.9/16:
“在复制返回值之后和执行函数外的任何表达式之前还有一个序列点 13)。C++ 中的几个上下文会导致对函数调用进行评估,即使在翻译单元中没有出现相应的函数调用语法。 [ 示例:新表达式的求值调用一个或多个分配和构造函数;参见 5.3.4。另一个示例,转换函数 (12.3.2) 的调用可能出现在没有函数调用语法出现的上下文中。-结束示例] 函数入口和函数出口处的序列点(如上所述)是函数调用的特征,无论调用函数的表达式的语法是什么。
注意我加粗的部分。这意味着在增量函数调用 (i.operator ++()
) 之后但在复合赋值调用 (i.operator+=
) 之前确实有一个序列点。
好吧。看完之前的回答后,我重新思考了自己的问题,特别是只有诺亚试图answer但我并不完全相信他的部分。
a[++i] = i;
情况1:
如果 a
是一个内置类型的数组。那么诺亚说的是对的。那是,
a[++i] = i 假设 a 是您的基本数组类型,甚至是用户定义的 one ,就没有那么幸运了。您在这里遇到的问题是我们不知道首先评估包含 i 的表达式的哪一部分。
所以 a[++i]=i
调用 undefined-behavior,或者结果未指定。不管它是什么,它都不是很好定义的!
PS:在上面的报价中,删除线当然是我的。
案例二:
如果 a
是重载 operator[]
的用户定义类型的对象,那么同样有两种情况。
如果重载的 operator[] 函数的返回类型是内置类型,那么 a[++i]=i 再次调用 undefined-behavior 或结果未指定。但是如果重载 operator[] 函数的返回类型是用户定义的类型,那么 a[++i] = i 的行为是明确定义的(据我所知),因为在这种情况下 a[++i ]=i 等价于写 a.operator[](++i).operator=(i);这与 a[++i].operator=(i); 相同。也就是说,赋值运算符 = 在 a[++i] 的返回对象上被调用,这似乎是非常明确的定义,因为当 a[++i] 返回时,++i 已经被计算,然后返回的对象调用 operator= 函数,将 i 的更新值作为参数传递给它。请注意,这两个调用之间有一个序列点。并且语法确保这两个调用之间没有竞争,并且 operator[] 将首先被调用,并且连续地,传递给它的参数 ++i 也将首先被评估。
将此视为 someInstance.Fun(++k).Gun(10).Sun(k).Tun();
,其中每个连续的函数调用都返回某个用户定义类型的对象。对我来说,这种情况更像是这样:eat(++k);drink(10);sleep(k)
,因为在这两种情况下,每个函数调用之后都存在序列点。
如果我错了,请纠正我。 :-)
k++
和 k
不由序列点分隔。它们都可以在评估 Sun
或 Fun
之前进行评估。 only 语言要求在 Sun
之前评估 Fun
,而不是在 Sun
的参数之前评估 Fun
的参数。我有点想再次解释同样的事情,但无法提供参考,所以我们不会从这里开始。
Sun
执行之前和之后有序列点,但 Fun
的参数 ++k
可以在此之前或之后进行评估。在 Fun
执行之前和之后有序列点,但 Sun
的参数 k
可以在此之前或之后进行评估。因此,一种可能的情况是 k
和 ++k
都在 Sun
或 Fun
被评估之前被评估,因此两者都在函数调用序列点之前,因此没有序列点分隔 { 6} 和 ++k
。
eat(i++);drink(10);sleep(i);
有何不同? ...即使是现在,您也可以说 i++
可能会在此之前或之后进行评估?
k
和 ++k
之间没有 no 序列点。在吃/喝示例中,有 is 作为 i
和 i++
之间的序列点。
eat()
和 sleep()
之间存在序列点,但在它们之间甚至没有一个参数。由序列点分隔的两个函数调用的参数如何属于 same 序列点?