【问题标题】:What is the extra argument used for when overloading the decrement operator? [duplicate]重载减量运算符时使用的额外参数是什么? [复制]
【发布时间】:2019-12-31 22:00:20
【问题描述】:

当我重载后减运算符时,我必须包含一个额外的参数:

#include <iostream>

template<class T>
class wrap
{
public:
    bool operator >(T &&v)
    {
        return value > v;
    }
    T operator --(int v) // Remove 'int v' and you get a compilation error
    {
        return (value -= 1) + 1;
    }
    wrap(T v)
    {
        value = v;
    }
private:
    T value;
};

int main(void)
{
    wrap<int> i(5);
    while(i --> 0)
        std::cout << "Why do we need the extra argument?\n";
    return 0;
}

如果我删除这个看似不需要的参数,我会收到编译错误:

test.cpp: In function ‘int main()’:
test.cpp:26:13: error: no ‘operator--(int)’ declared for postfix ‘--’ [-fpermissive]
     while(i --> 0)
           ~~^~

这个参数有什么用?它的价值代表什么?

【问题讨论】:

  • 引用重复中的答案确实解释了int 参数是一个虚拟变量。

标签: c++ operator-overloading decrement postfix-operator


【解决方案1】:

operator++ - pre-increment and post-increment 有两个独立但相关的重载。两者都是可覆盖的。由于它们都具有相同的名称,因此 C++ 的设计者必须决定一种语法以让编译器区分它们。他们选择在后自增运算符上使用虚拟的int 参数。

当你编写这样的代码时:

wrap<int> i(5);
++i; // <-- calls i.operator++();

编译器发出代码来调用无参数的operator++()

当你写这样的代码时:

wrap<int> i(5);
i++; // <-- calls i.operator++(0);

编译器发出代码来调用带参数的operator++(int),传递0 作为虚拟参数。

参数的没有意义,它的存在就足以让operator++的每个重载都被单独覆盖,因为它们具有不同的签名。

顺便说一句,同样的规则也适用于前/后减量operator--

【讨论】:

  • 这是否意味着调用my++++my 相比会产生轻微的额外开销?
  • @PatrickParker 是的,因为额外的参数被压入/弹出调用堆栈,并且还因为后增量还返回旧值在增量之前的副本。预增量返回对增量值的引用,而不是副本
【解决方案2】:

每个已批准的 ANSI 和 ISO C++ 标准以及每个工作草案都对此进行了描述,尽管版本之间的精确措辞有所不同。

例如,根据“工作草案,C++ 编程语言标准”,文档编号 N4659,日期为 2017-03-21,第 16.5.7 节“递增和递减 [over.inc]”,第 1 段

名为operator++ 的用户定义函数实现了前缀和后缀++ 运算符。如果这个函数是一个没有参数的非静态成员函数,或者一个有一个参数的非成员函数,它为该类型的对象定义了前缀递增运算符++。如果函数是具有一个参数的非静态成员函数(应为 int 类型)或具有两个参数的非成员函数(第二个 其中应为 int 类型),它为该类型的对象定义了后缀增量运算符++。当使用++ 运算符调用后缀增量时,int 参数的值为零。

在上一段的末尾,有一个脚注 134 的引用,它说

显式调用operator++,就像在a.operator++(2) 这样的表达式中,没有特殊属性:operator++ 的参数是2

在第 1 段之后,标准甚至提供了一个示例。

[ 例子:

struct X {
    X& operator++(); // prefix ++a
    X operator++(int); // postfix a++
};

struct Y { };
  Y& operator++(Y&); // prefix ++b
  Y operator++(Y&, int); // postfix b++

void f(X a, Y b) {
    ++a; // a.operator++();
    a++; // a.operator++(0);
    ++b; // operator++(b);
    b++; // operator++(b, 0);

    a.operator++(); // explicit call: like ++a;
      a.operator++(0); // explicit call: like a++;
      operator++(b); // explicit call: like ++b;
      operator++(b, 0); // explicit call: like b++;
}

结束示例 ]

第 2 段接着说

前缀和后缀减量运算符-- 的处理方式类似。

【讨论】:

    【解决方案3】:

    来自cppreference

    int 参数是一个虚拟参数,用于区分运算符的前缀和后缀版本。当调用用户定义的后缀运算符时,该参数中传递的值始终为零,尽管可以通过使用函数调用表示法(例如,a.operator++(2)operator++(a, 2))调用运算符来更改它。

    虽然我强烈建议不要围绕这些显式调用创建功能(对于其他任何人来说,这看起来非常奇怪)。

    【讨论】:

    • ISO C++ 标准对此有何评论?我没有使用微软的编译器。
    • @JL2210 C++ 标准同意——这是该语言的核心方面。我只是懒惰并选择了 MS,因为它们在谷歌列表中名列前茅。不过,我已经编辑使用 cppreference,这可能是一个更好的来源。
    • 我可以接受另一个答案吗?其他一些会稍微详细一些。
    • @JL2 如果您觉得另一个答案更好地回答您的问题/解决了您的问题,系统会让您不接受我的并接受另一个。你不需要我的许可! :)
    • 好的。只是不想让任何人生气。
    【解决方案4】:

    因为您使用的是后缀减量:

     while (i-- > 0)
    

    应该减少i但返回原始值:

    T operator--(int) // dummy int param for postfix
    {
        T temp = value;
        value -= 1;
        return temp;
    }
    

    虚拟的int参数只是一个占位符,用于区分后缀和前缀减量:

    T operator--() // no param for prefix
    {
        value -= 1;
        return value;
    }
    

    【讨论】:

      猜你喜欢
      • 2011-05-09
      • 2015-06-23
      • 2016-09-28
      • 1970-01-01
      • 2020-03-25
      • 1970-01-01
      • 2015-08-22
      • 1970-01-01
      相关资源
      最近更新 更多