前缀/后缀递增运算符

23

我想确保我正确理解传值和传引用的区别。特别是,我正在研究对象的前缀/后缀版本的递增运算符 ++。

假设我们有以下类 X

class X{
private:
    int i;
public:
 X(){i=0;}
 X& operator ++ (){ ++i; return *this; } //prefix increment

 X operator ++ (int unused){ //postfix increment
  X ret(*this);
  i++;
  return ret;
 }

 operator int(){ return i; } //int cast
};

首先,我是否正确地实现了前缀/后缀递增运算符?

其次,与前缀运算符相比,后缀运算符在内存效率上如何?具体来说,当使用每个版本的运算符时,创建了多少 X 对象副本?

通过讲解返回引用和返回值的方式可以帮助我理解究竟发生了什么。


编辑:例如,对于以下代码...

X a;
X b=a++;

现在a和b是别名吗?


在后缀运算符中没有必要使用后缀递增i。实际上,我会像FredOverflow建议的那样调用前缀版本。在我看来,这比重新实现递增更符合惯用法(即使在这里实现是微不足道的)。并且摆脱那个隐式转换操作符。否则它会伤害你。(我最后一次编写隐式转换操作符是在2001年,一两年后我发现它引起了微妙的错误并将其删除 - 就像之前所有的操作符一样。BTDTGTLS。) - sbi
3个回答

24

在后缀操作中,更加惯用的方式是调用对象自身的前缀递增操作:

X operator++(int)
{
    X copy(*this);
    ++*this;         // call the prefix increment
    return copy;
}

X 对象的增量逻辑完全包含在前缀版本中。


没错,这让我不必再发同样的更正了。我给你点赞。 - sbi

17

这是一个正确的实现。通常情况下,后缀运算符的性能会更差,因为在执行递增操作之前必须创建另一个副本(这就是我养成始终使用前缀的习惯的原因)。

通过引用返回,您正在返回对当前对象的左值引用。编译器通常通过返回当前对象的地址来实现这一点。这意味着返回对象就像返回一个数字一样简单。

但是,通过值返回时,必须进行复制。在返回期间需要复制更多的信息(而不仅仅是一个地址),并且需要调用复制构造函数。这就是性能损失的原因。

您的实现效率看起来与典型的实现相当。

编辑: 关于您的添加说明,不,它们不是别名。您已经创建了两个单独的对象。当您通过值返回(并从后缀递增运算符内部创建新对象)时,该新对象被放置在不同的内存位置中。

然而,在以下代码中,a和b别名:

 int a = 0;
 int& b = ++a;
b是一个指向a的地址。

2
总体上正确,可能会有返回值优化的情况(http://en.wikipedia.org/wiki/Return_value_optimization)。 - Nikolai Fetissov

3

您的操作符已正确实现。

在前缀操作符中,不会复制X。

在后缀操作符中,为ret创建了一个副本,并且在从函数返回时可能会创建另一个副本,但是所有编译器都将省略此副本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接