C++运算符重载

15
为什么下面的C++程序会输出“ACCA”?为什么operator int()会被调用两次?
#include "stdafx.h"
#include <iostream>

using namespace std;

class Base {
public:
    Base(int m_var=1):i(m_var){
        cout<<"A";
    }
    Base(Base& Base){
        cout<<"B";
        i=Base.i;
    }
    operator int() {
        cout<<"C";
        return i;
    }
private:
    int i;
};

int main()
{
    Base obj;
    obj = obj+obj;
    return 0;
}

FYI;这是一个构造体,旨在创建ACCA,作为读者的练习。 - Captain Giraffe
9个回答

30

首先,这一行:

Base obj;

通过选择接受整数参数的构造函数,并设置默认值为 1,默认构造对象 obj。这是导致第一个 A 被打印到标准输出的原因。

然后,该表达式:

obj + obj

需要选择一个有效的重载operator +。在这种情况下,由于obj有一个用户定义的转换到int,所以选择了内置的operator +,并且两个参数都被转换为int。这就是导致输出了两个C到标准输出的原因。

然后,在以下语句中对obj进行赋值:

obj = obj + obj

需要调用隐式生成的Baseoperator=。隐式生成的operator=的签名为:

Base& operator = (Base const&);

这意味着等号右侧的表达式,类型为int,必须被转换为一个临时的Base对象,然后用于赋值给obj (隐式生成的operator=的引用参数被绑定到这个临时对象)。

但是,从int创建这个临时对象需要调用接受int参数的Base的转换构造函数,这就导致第二个A被输出到标准输出流中。


9
由于您没有重载operator+,所以会调用两次operator int()。编译器不知道如何将一个Base加到另一个Base上,因此它们被转换为int(因为您教给它如何做到这一点),而编译器知道如何执行此操作。以下代码将打印ADA:
#include <iostream>

using namespace std;

class Base {
public:
    Base(int m_var=1):i(m_var){
        cout<<"A";
    }
    Base(Base& Base){
        cout<<"B";
        i=Base.i;
    }
    operator int() {
        cout<<"C";
        return i;
    }
    int operator+(Base& Base)
    {
        cout<<"D";
        return i+Base.i;
    }
private:
    int i;
};

int main()
{
    Base obj;
    obj = obj+obj;
    return 0;
}

5
当您构建对象时,您将获得第一个“A”:
 Base obj;

当你指定添加obj + obj时,编译器需要找到一种方法在obj上使用+。由于你没有为Base重载operator+,所以每个方程式的每一侧都会调用转换为int()

obj+obj

这会输出 "CC"

然后,你给 obj 赋值,objBase 类型的,所以运行能接受一个 int 参数的构造函数(来自于 int() 运算符的 i + i),该构造函数会输出 "A":

obj = obj+obj; // Assignment prints "A"

5
obj = obj+obj;
      ^^^--------obj converted to int here
          ^^^----obj converted to int here
^^^^^------------Base(int) ctor and default operator= called here

重载类型转换运算符通常不是一个好主意,除非你了解成本并且确信在你的特定情况下收益超过成本。


2
以下的C++程序如何计算出"ACCA"?
在输出中第一个字母是'A'。这个输出与以下代码相关:
Base obj;

...其中你正在创建一个 Base 的新实例。

下一行有点复杂:

obj = obj+obj;

通常情况下,这会被翻译为obj.operator+( obj ),但是由于在类Base中没有重载运算符+,所以此翻译无效。剩下的可能性是运算符+实际上是数字加法运算符。
是的,这是可能的,因为您提供了一个转换为int的强制转换。因此,可以将等式的每个术语转换为int...因此会调用operator int两次。调用operator int的实际次数取决于激活的优化。例如,编译器可能意识到两个术语是相同的,然后在第一次调用operator int之后创建新的临时变量。在这种情况下,您将看到CA而不是CC。

最后,赋值表达式obj.operator=(temp)被执行。而此处的关键字是temp。为了让默认operator=起作用,因为它没有被重载,你必须在右边有一个Base对象。实际上,由于Base使用一个int来构建新实例,这是可能的。好的,那么obj + obj的结果是一个int(称之为'x'),编译器创建了一个临时的Base类对象,它使用数字x构造,就像下面一行被执行的那样:

Base temp( x );

这是最终看到的字母是'A'的方式。同样,许多编译器可以避免在某些情况下构建临时文件,因此可能不会在结尾看到'A'。

请注意这一行:

obj = obj + obj

因此被分���为:

int x = ( (int) obj ) + ( (int) obj );
Base temp( x );
obj = temp;

最终指令的结果就像是 obj 所在内存将被填充上 temp 的内容(这是默认复制构造函数的作用,它会为类的每个成员执行 operator=,详见“三法则”)。

运算符重载涉及许多问题,如果您对语言没有更深入的了解,可能会出现未预料到的情况,正如您所看到的。同时还需注意,像Java这样的语言完全禁止其使用,而C#则从一个受控的角度允许它的使用。


1
在表达式obj+obj中,您两次引用了obj,并且每次引用都必须转换为整数。这样的转换可能是“有状态的”(尽管这是一个可怕的想法) - 也就是说,它可以通过设计每次调用时返回不同的值。毕竟,obj所代表的值可能会改变(它可能是一个计数器或类似的东西)。因此,编译器必须为每个引用重新评估它。

由于 operator int() 没有标记为 const,函数签名直接告诉编译器它应该期望调用会修改对象的状态。即使是一个 const operator,第二次调用也不能被优化掉,因为可能存在像 cout 这样的东西(而且在这里确实存在)。 - celtschk
我们可以将具有副作用(例如写入标准输出)的函数视为每次调用时返回宇宙的新版本,这是解释“每次调用返回不同的值”的另一种方式。 - Daniel Earwicker

1
每个操作数只调用一次,无论它是否为同一实例。
 obj = obj+obj;

它将第一个操作数转换为int,然后是第二个操作数。

如果要避免这种隐式转换,您需要重载运算符+。


1
它调用了operator int()两次,因为在相加之前需要将两个obj都转换为int类型。

1

第一个 A 来自

Base obj;

两个 C 来自将 obj+obj 转换为 int,因为您没有重载 operator +

最后一个 A 来自将结果的 int 转换为 objobj = 转换。


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