C++ 编译器如何扩展前缀和后缀运算符++()?

How does a C++ compiler expand prefix and postfix operator++()?

考虑:

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'}
};

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

例如,当我这样写的时候:

Example e = 1;
e++;

我预计它会扩展到 "e.operator++(int)",或者更进一步,我预计

e++(2);

扩展为 "e.operator++(2)." 之类的东西,但我得到的是编译器抱怨一些 "no match for call to '(Example) (int)'"。

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

再玩一些,我得到了:

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

其中打印了2,然后:

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

其中打印了 3.

我明白 (++e) returns 对对象的引用,然后 post 递增 1,所以这是有道理的。我还怀疑“++e++”在这里给予 postfix 运算符优先级(正如我在另一个 post 中读到的),所以这是递增 postfix 返回的临时变量操作员。这也是有道理的。这让我想知道

这样的表达式如何
++++e
++e++++
++++e++
++++e++++

已扩展(它们全部编译并且 运行 具有预期结果)。

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

What is the purpose of the placeholder variable in "operator++(int)"?

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

how does the compiler know which operator++() to call,

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

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

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

++e++++;

要解析此语句,您需要了解这些解析规则:

  • 令牌由 "maximal munch" 解析,即这意味着 ++ e ++ ++;(而不是一些一元-+ 运算符)。
  • 语言语法根据这些标记确定哪些表达式是哪些运算符的操作数。这个过程可以概括为precedence table.

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

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

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

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

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

注意。我的回答只谈到重载运算符是一个成员函数。也可以作为非成员重载 ++(两种形式)。在那种情况下,行为将与我的描述相同,但 "written in function call notation" 表达式将采用不同的语法。