未定义的行为和序列点重新加载
请考虑以下主题的续篇:
以前的安装
未定义的行为和顺序点
让我们重温一下这个有趣而复杂的expression(斜体字是从上面的话题*微笑*中获得的):
i += ++i;
我们说这调用了未定义的行为。 我假设说这个时候,我们隐含地认为i
types是内置types之一。
如果i
的types是用户定义的types呢? 说它的types是在这篇文章后面定义的Index
(见下文)。 它会调用未定义的行为吗?
如果是,为什么? 这不等于写i.operator+=(i.operator++());
甚至在句法上更简单i.add(i.inc());
? 或者,他们是否也调用未定义的行为?
如果不是,为什么不呢? 毕竟,对象i
在连续的序列点之间被修改两次 。 请回想一下经验法则: expression式只能在连续的“序列点之间修改一个对象的值 ,如果i += ++i
是一个expression式,那么它必须调用未定义的行为i.operator+=(i.operator++());
和i.add(i.inc());
还必须调用undefined-行为似乎是不真实的(据我所知)
或者, i += ++i
不是一个expression式开始? 如果是这样,那么它是什么, expression的定义是什么?
如果它是一个expression式,并且同时它的行为也是明确定义的,那么它意味着与某个expression式相关联的序列点的数量在某种程度上取决于expression式中涉及的操作数的types 。 我是否正确(甚至部分)?
顺便说一下,这个expression呢?
//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; } };
它看起来像代码
i.operator+=(i.operator ++());
就序列点而言,完美无缺。 C ++ ISO标准的第1.9.17节讲述了序列点和function评估:
当调用一个函数(函数是否是内联函数)时,在执行函数体中的任何expression式或语句之前,所有函数参数(如果有的话)的求值之后都有一个序列点。 在复制返回值之后和执行函数之外的任何expression式之前,还有一个顺序点。
例如,这将表明i.operator ++()
作为operator +=
的参数在其求值之后有一个序列点。 总之,因为重载操作符是函数,所以正常的顺序规则适用。
好的问题,顺便说一下! 我真的很喜欢你如何强迫我理解我已经认为我知道的一种语言的细微差别(并认为我认为我知道)。 🙂
正如其他人所说,你的i += ++i
例子与用户定义的types一起工作,因为你正在调用函数,而函数包括顺序点。
另一方面,假设a
是您的基本数组types,或者甚至是用户定义的, a[++i] = i
并不是那么幸运。 你在这里得到的问题是,我们不知道包含i
的expression式的哪一部分首先被评估。 这可能是++i
被评估,传递给operator[]
(或原始版本),以便在那里检索对象,然后i
的值传递给那个(这是我增加后)。 另一方面,也许后面的方面是先评估,存储以便以后分配,然后评估++i
部分。
模拟文字出现在我的脑海里
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 );
我认为这是明确的:
从C ++草案标准(n1905)§1.9/ 16:
“在复制一个返回值之后,在执行函数之外的任何expression式之前,还有一个顺序点。)C ++中的几个上下文导致函数调用的评估,即使翻译单元中没有出现相应的函数调用语法。 [ 例子 :新expression式的求值调用一个或多个分配和构造函数;参见5.3.4。另一个例子,转换函数(12.3.2)的调用可能出现在没有函数调用语法出现的上下文中。 例子 ] 无论调用函数的expression式的语法如何,函数调用和函数退出的顺序点(如上所述)都是被调用的函数调用的特征。
注意我粗体的部分。 这意味着在增量函数调用( i.operator ++()
)之后但在复合赋值调用( i.operator+=
)之前的确有一个序列点。
好的。 经过前面的回答,我重新思考了自己的问题,特别是这个只有诺亚试图回答的部分,但是我不完全相信他。
a[++i] = i;
情况1:
如果a
是内置types的数组。 那么诺亚所说的是正确的。 那是,
a [++ i] =我不是那么幸运,假设a是你的基本数组types,
甚至是用户定义的一个。 你在这里得到的问题是,我们不知道包含我的expression式的哪个部分首先被评估。
所以a[++i]=i
调用未定义的行为,或者结果是未指定的。 不pipe它是什么,它都没有明确的定义!
PS:在上面的报价中, 透 当然是我的。
案例2:
如果a
是用户定义types的一个对象,它重载了operator[]
,那么有两种情况。
- 如果重载的
operator[]
函数的返回types是内置types,那么a[++i]=i
调用未定义的行为,或者结果是未指定的。 - 但是,如果重载的
operator[]
函数的返回types是用户定义的types,那么a[++i] = i
的行为是明确定义的(据我所知),因为在这种情况下a[++i]=i
相当于写a.operator[](++i).operator=(i);
这与a[++i].operator=(i);
。 也就是说,赋值operator=
在a[++i]
的返回对象上被调用,这似乎是非常明确的,因为在a[++i]
返回的时候,++i
已经被计算了,然后返回的对象调用operator=
函数将i
的更新值作为parameter passing给它。 请注意,这两个调用之间有一个顺序点 。 语法确保了这两个调用之间没有竞争,并且operator[]
会被首先调用,并且连续地,++i
传递给它的参数++i
也会被首先评估。
把这个想成someInstance.Fun(++k).Gun(10).Sun(k).Tun();
其中每个连续的函数调用都返回一些用户定义types的对象。 对我来说,这种情况似乎更像这样: eat(++k);drink(10);sleep(k)
,因为在这两种情况下,每个函数调用后都存在序列点。
如果我错了,请纠正我。 🙂