C++运算符重载与无名对象引用问题的一点思考

问题的发现

今天复习C++运算符重载时候的意外发现:关于类的临时对象以及引用的一些思考。来源于C++程序设计(小红书)上给出了“+”号运算符重载的样例。

基本知识回顾

重载运算符的习惯

  • C++规定,赋值运算符=、下标运算符[]、函数调用运算符()、成员运算符->必须作为成员函数。
  • 流插入运算符<< 和流提取运算符 >>、类型转换运算符不能定义为类的成员函数,只能作为友元函数。
  • 一般将单目运算符和复合运算符重载为成员函数
  • 一般将双目运算符重载为友元函数。 ## Next So 根据上述的习惯,我对着屏幕敲了书上的样例的类定义的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Complex
{
public:
Complex() {
real=0;
imag=0;
}
Complex(double d){
real=d;
imag=0;
}
Complex(double r,double i){
real =r;
imag=i;
}
friend Complex operator + (Complex &a,Complex &b);
friend ostream& operator << (ostream &,Complex &);
private:
double real;
double imag;
};

Complex operator +(Complex &a,Complex &b)
{
Complex temp;
temp.real=a.real+b.real;
temp.imag=a.imag+b.imag;
return temp;
}

ostream& operator << (ostream& output,Complex &a )
{
cout<<"("<<a.real<<","<<a.imag<<"i)";
return output;
}

int main()
{
Complex c1(3,4),c2(5,-10),c3;
c3=c1+4.0;
cout<<c3<<endl;
c3=c3+c1;
cout <<c3<<endl;
}

写完感觉看着是没什么问题对吧(我真觉的没什么问题)。 然而运行了一下就报错了,那...报什么错了?

[Error] no match for 'operator+' (operand types are 'Complex' and 'double')
[Note] candidates are:
[Note] Complex operator+(Complex&, Complex&)
[Note] no known conversion for argument 2 from 'Complex' to 'Complex&'

哦哦看了下报错,原来是重载函数写的问题,我先去查了一下代码。

课本上给出的重载运算符声明和定义是:

friend Complex operator + (Complex a,Complex b);
...

我自己看走眼敲成了:

friend Complex operator + (Complex &a,Complex &b);
...

看出问题在哪了么?没错就是一个"&"的差别,即形参是Complex类对象的引用还是Complex类对象。

问题展开与解决

去掉"&"后程序果然可以正常运行的。不过我考虑了一下程序的实现过程,不对啊,一个简单的相加,我传递一个类对象的引用 不应该出现什么问题啊,"+"本身又不会对引用的对象进行什么操作,返回类型是一个Complex类的对象。问题出现在哪里了?

一些奇葩(正经)的实验

于是乎我把之前写的简单的"+"重载的程序,函数的形参都改为对象的引用,再运行。结果更奇怪了,除了上述程序,都没有报错。 这个程序又独特在哪里了?

问题点

再返回去读一读程序,这个样例用到了转换构造函数。

c3=c1+4.0;

在保留注释掉以后程序果然可以正常运行了,问题确实出现在这里。既然是转换构造函数的问题,隐式调用(默认执行的)方式不可行,那我试一试显式的

c3=c1+Complex(4.0);

然而...报错依旧,只有微小的变化

[Error] no match for 'operator+' (operand types are 'Complex' and 'Complex')

对照之前的

[Error] no match for 'operator+' (operand types are 'Complex' and 'double')

原来最开始的程序并没有成功的调用转换构造函数。编译器没有检测到? 看来是哪里出了什么之前没考虑过的问题。

思考

(不摔桌!不摔桌!冷静!)如果通过引用进行参数传递,可以免去建立实参的拷贝,空间和时间上都可以得到优化, 逻辑推理一下,那这种传参的方式应该被推行啊,相近的例子就是流提取和流插入运算符的重载,第二个参数都是自定义类的引用。

那...为何"+"的重载不采用这种方式呢?看来是有问题的,结合之前的实验,在不出现转换构造函数调用的情况下,形参为引用是行得通的。 看来这个问题和转换构造函数也相关。

再回过头来看报错信息

[Error] no match for 'operator+' (operand types are 'Complex' and 'Complex')
[Note] candidates are:
[Note] Complex operator+(Complex&, Complex&)
[Note] no known conversion for argument 2 from 'Complex' to 'Complex&'

最后一句:Complex到Complex&转换出了问题,这个比较稀奇。从类对象到类对象的引用不应该是顺理成章的事情么,别名而已。Emm问题就在这个别名身上

引用<->别名

转换构造函数出现的无名对象

转换构造函数是构造函数的重载,在使用转换构造函数的过程中,我们可以建立一个有名对象,也可以建立一个无名对象。 书上中的代码样例

Complex c1(3.5); //调用转换构造函数建立对象c1
Complex(3.6); //调用转换构造函数建立无名对象,合法,但无法使用。

就是这个无名对象的锅,我们回过头来看最开始发现的问题点:

c3=c1+Complex(4.0);

由于Complex(4.0)建立的是一个无名对象,而采用我错误写出的以引用作为形参的 "+"运算符重载,会发生什么?没有名字,自然无法引用啊。 这也就解释了报错信息为什么Complex到Complex&转换出了问题。无名的对象无法建立引用(没名字你怎么给它找个别名)。

事后诸葛亮

根据简单的实验其实能得到一些结论 - 涉及转换构造函数的运算符重载,形参都应该是类对象而不是类对象的引用。 - C++自己实现的"="应该也是用类对象作形参,因为 c1=Complex(3.6); 这一句是可以正常执行的。

算作对引用以及构造函数的一个探索吧:)