85

将此主题视为以下主题的续集:

上一部分
未定义的行为和序列点

让我们重温一下这个有趣复杂的表达方式(斜体词组取自上述主题 *smile* ):

i += ++i;

我们说这调用了未定义的行为。我假设当这样说时,我们隐含地假设type ofi是内置类型之一。

如果类型i用户定义的类型怎么办?说它的类型是Index本文后面定义的类型(见下文)。它还会调用未定义的行为吗?

如果是,为什么?它不等同于写作i.operator+=(i.operator++());甚至语法上更简单 i.add(i.inc());吗?或者,他们是否也调用了未定义的行为?

如果没有,为什么不呢?毕竟,对象在连续序列点之间i被修改了两次。请回忆一下经验法则:一个表达式只能在连续的“序列点”之间修改一个对象的值。如果 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;
        }
};
4

5 回答 5

48

它看起来像代码

i.operator+=(i.operator ++());

在序列点方面工作得很好。C++ ISO 标准的第 1.9.17 节对序列点和函数求值进行了说明:

调用函数时(无论函数是否内联),在对所有函数参数(如果有)求值之后都会有一个序列点,该序列点发生在函数体中的任何表达式或语句执行之前。在复制返回值之后和执行函数外的任何表达式之前还有一个序列点。

这将表明,例如,i.operator ++()作为参数 tooperator +=在其评估之后具有一个序列点。简而言之,因为重载运算符是函数,所以适用正常的排序规则。

顺便说一句,好问题!我真的很喜欢你强迫我理解一种我已经认为我知道(并且认为我认为我知道)的语言的所有细微差别。:-)

于 2011-01-09T08:47:32.700 回答
12

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 );
于 2011-01-09T16:59:48.533 回答
11

正如其他人所说,您的i += ++i示例适用于用户定义的类型,因为您正在调用函数,并且函数包含序列点。

另一方面,a[++i] = i假设这a是您的基本数组类型,甚至是用户定义的数组类型,就不是那么幸运了。您在这里遇到的问题是我们不知道i首先计算表达式的哪一部分。可能是++i被评估,传递给operator[](或原始版本)以便在那里检索对象,然后将值i传递给那个(在i增加之后)。另一方面,也许后者首先被评估,存储以供以后分配,然后对++i部分进行评估。

于 2011-01-09T09:35:22.560 回答
8

我认为它定义明确:

来自 C++ 标准草案 (n1905) §1.9/16:

“在复制返回值之后和执行函数外的任何表达式之前还有一个序列点 13)。C++ 中的几个上下文会导致对函数调用进行评估,即使在翻译单元中没有出现相应的函数调用语法。 [示例:新表达式的求值调用一个或多个分配和构造函数;参见 5.3.4。另一个示例,转换函数 (12.3.2) 的调用可能出现在没有函数调用语法出现的上下文中。-结束示例] 函数入口和函数出口处的序列点(如上所述)是评估的函数调用的特征,无论表达式的语法如何调用函数的可能是。"

注意我加粗的部分。i.operator ++()这意味着在增量函数调用 ( ) 之后但在复合赋值调用 ( ) 之前确实有一个序列点i.operator+=

于 2011-01-09T08:48:46.830 回答
6

好的。看完之前的回答,我重新思考了自己的问题,尤其是这部分只有诺亚试图回答,但我并不完全相信他。

a[++i] = i;

情况1:

Ifa是一个内置类型的数组。那么诺亚说的是对的。那是,

假设 a 是您的基本数组类型, a[++i] = i 就没有那么幸运了,甚至是用户定义的. 您在这里遇到的问题是我们不知道首先评估包含 i 的表达式的哪一部分。

所以a[++i]=i调用 undefined-behavior,或者结果是未指定的。不管它是什么,它都不是很好定义的!

PS:在上面的报价中,删除线当然是我的。

案例二:

如果a是重载 的用户定义类型的对象operator[],那么同样有两种情况。

  1. 如果重载operator[]函数的返回类型是内置类型,则再次a[++i]=i调用 undefined-behavior 或结果未指定。
  2. 但是,如果重载operator[]函数的返回类型是用户定义的类型,那么 的行为a[++i] = i是明确定义的(据我所知),因为在这种情况下a[++i]=i等同于a.operator[](++i).operator=(i);a[++i].operator=(i);. 也就是说,在 的返回对象上operator=调用赋值,这似乎是非常明确的定义,因为到返回时,已经被评估,然后返回的对象调用函数将更新的值作为参数传递给它。请注意,这两个调用之间有一个序列点。并且语法确保这两个调用之间没有竞争,并且a[++i]a[++i]++ioperator=ioperator[]将首先被调用,并且连续地,++i传递给它的参数也将首先被评估。

可以将其视为someInstance.Fun(++k).Gun(10).Sun(k).Tun();每个连续的函数调用都返回某个用户定义类型的对象。对我来说,这种情况更像是这样的:eat(++k);drink(10);sleep(k),因为在这两种情况下,每个函数调用之后都存在序列点。

如果我错了,请纠正我。:-)

于 2011-01-09T10:39:04.703 回答