【问题标题】:How does a C++ compiler expand prefix and postfix operator++()?C++ 编译器如何扩展前缀和后缀 operator++()?
【发布时间】:2017-10-18 18:29:04
【问题描述】:

考虑:

class Example
{
private:
    int m_i;

public:
    Example(int i) : m_i{i} {}

    //Post-fix
    Example operator++(int) {m_i++; return *this;}

    //Pre-fix
    Example& operator++() {m_i++; return *this;}

    void print() const {std::cout << m_i << '\n'}
};

我正在对此进行试验,以确定编译器如何扩展对前缀和后缀运算符的调用。

例如,当我写这样的东西时:

Example e = 1;
e++;

我预计它会扩展为“e.operator++(int)”之类的东西,或者更进一步,我预计

e++(2);

扩展为“e.operator++(2).”之类的东西,但我得到的是编译器抱怨一些“不匹配调用 '(Example) (int)'”。

接下来我很好奇“++e”如何神秘地扩展为“e.operator++()”,即返回引用的那个。

玩了一些,我最终得到了:

Example e = 1;
++e++;
e.print();

其中打印了 2,然后:

Example e = 1;
(++e)++;
e.print();

其中打印了 3。

我知道 (++e) 返回一个对对象的引用,该对象随后会加一,所以这是有道理的。我还怀疑“++e++”在这里给了后缀运算符优先级(正如我在另一篇文章中读到的那样),所以这是增加后缀运算符返回的临时变量。这也是有道理的。这让我想知道表达式是怎样的

++++e
++e++++
++++e++
++++e++++

被扩展(它们都编译并运行并获得预期结果)。

真的,内部到底发生了什么,编译器如何知道要调用哪个 operator++(),以及这些表达式是如何扩展的(尤其是在前缀情况下)? “operator++(int)”中占位符变量的作用是什么?

【问题讨论】:

  • 占位符的存在只是为了让两个运算符在重载解析期间不同。该参数未指定,必须保持未使用状态。
  • 阅读上面的链接,最后你做的所有例子都缺少序列点。
  • 要使您的后缀运算符在语义上符合预期,请将其更改为:Example operator++(int) { return Example(m_i++); }
  • 重新打开;这里没有未定义的行为,我认为通用的“运算符重载”线程不是这个问题的好答案
  • @CoryKramer 每个函数的进入和退出都有一个序列点(使用 C++11 之前的术语);和重载的运算符是函数

标签: c++ operator-overloading operators prefix postfix-operator


【解决方案1】:

“operator++(int)”中占位符变量的作用是什么?

因为++ 运算符有两个不同的功能:后缀-++ 和前缀-++。因此,在重载它时,必须有两个不同的函数签名。

编译器如何知道调用哪个operator++(),

当您的代码使用前缀-++(例如:++e;)时,会调用签名为operator++() 的函数。当您的代码使用 postfix-++(例如:e++;)时,将调用签名为 operator++(int) 的函数,编译器将提供一个未指定的虚拟参数值。

从技术上讲,operator++(int) 的实现可以使用虚拟参数值。你可以通过写e.operator++(5);而不是e++;来传递你自己的价值。但这会被认为是不好的编码风格——重载运算符时,建议保留内置运算符的语义,以免混淆阅读代码的人。

请注意,您当前的 postfix-++ 实现不遵守此规则:正常语义是应返回前一个值;但您的代码会返回更新后的值。

++e++++;

要解析这个语句,你需要了解这些解析规则:

  • 令牌由“最大咀嚼”解析,即这意味着++ e ++ ++;(而不是一些一元-+ 运算符)。
  • 语言语法根据这些标记确定哪些表达式是哪些运算符的操作数。这个过程可以总结为precedence table

查询该表达式的表格会告诉您:++(((e++)++))。使用我之前提到的扩展,这可以用函数调用表示法来写:

((e.operator++(0)).operator++(0)).operator++();

在这种情况下,这些函数需要从左到右调用,因为成员函数在被调用的表达式被计算之前不能被输入。

因此,假设我们在此语句之前有Example e(1);,则以下函数调用按此顺序发生:

  • e.operator++(int) - 将e.m_i 设置为2 并返回一个临时的(我将其称为temp1 作为伪代码),temp1.m_i 作为2
  • temp1.operator++(int) - 将temp1.m_i 设置为3,并返回temp2,其m.i3
  • temp2.operator++() - 将 temp2.m_i 设置为 4 并返回对 temp2 的引用。

注意。我的回答只谈论重载运算符是成员函数。也可以将++(两种形式)作为非成员重载。在这种情况下,我的描述中的行为不会改变,但是“用函数调用表示法编写”表达式会采用不同的语法。

【讨论】:

  • 那么像“++++e++”这样的扩展呢?我假设我对后缀具有优先权的假设没有错,这意味着编译器看到的东西(相当粗略)等同于(++(++(e++)))。它是否正确?将此扩展为实际的函数调用,会是 e.operator++(int).operator++().operator()++?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-20
相关资源
最近更新 更多