详解C++ 运算符重载中返回值的坑
相信不少朋友在学习运算符重载的时候,都会被参数与返回值应该是左值引用,还是右值引用,还是const常量所困扰。当然我无法一一枚举,这次先讲一下返回值的坑 (没错就是我亲手写的bug)
E0334 “Myclass” 没有适当的复制构造函数
其实这个问题的根源是,没有定义常量参数类型的拷贝构造函数所致
先来看看代码
//头文件head.h class Myclass { private: int a; public: Myclass(int b=0):a(b) {} //构造函数 Myclass(Myclass& c); //复制构造函数 ~Myclass(){} //析构函数 Myclass operator+(Myclass& d); //重载+运算符 friend ostream& operator<<(ostream& os ,const Myclass& d); //重载<<运算符 }; //以下是定义 Myclass::Myclass(Myclass& c) { a = c.a; } Myclass Myclass::operator+(Myclass& d) { return Myclass(d.a+a); //!!此处报错 } ostream& operator<<(ostream& os,const Myclass& d) { os << d.a << std::endl; return os; } //main.cpp #include"head.h" int main() { Myclass a1(5); Myclass a2(12); Myclass sum = a1 + a2; //!!此处报错 std::cout << sum; }
代码在VS中,又出现了令人讨厌的小红线,没有适当的复制构造函数,这就有疑问了, 不是明明有个构造函数Myclass(int b=0):a(b) {}吗,参数是int很合适啊? 于是,我们定义一个临时变量temp,再将它返回,此时会隐式调用拷贝构造函数而后返回一个副本后原来的temp就die了,因此返回值不可以是引用。下面是代码
Myclass Myclass::operator+(Myclass& d) { Myclass temp(d.a + a); return temp; }
此时第一处报错消失了,但是第二处报错依然存在,而且仍为 “没有适当的复制构造函数”,这就说明了,我的入手方向应该是拷贝构造函数
经过博主的调试,得知是因为函数的返回值是一个纯右值,为了验证这个想法,使用了右值引用,来接收这个纯右值(当然,右值引用更多的是用在移动构造函数上,将 将亡值“偷”出来)
#include"head.h" int main() { Myclass a1(5); Myclass a2(12); Myclass&& sum = a1 + a2; }
果然,它不报错了
但是考虑到实用性,总不能让用户今后做个加法都要用右值引用接收吧,因此,我们要从源头解决,即重载拷贝构造函数。
值得思考的是,右值不就是被赋值的那个吗,为什么用Myclass&& sum = a1 + a2;无法赋值呢?众所周知,Myclass&& sum = a1 + a2;调用的是拷贝构造函数,类不同于基本数据类型,它要通过程序员来设置一系列的功能,我们没有设置接受,Myclass类型的右值的功能,只定义了接受int类型的右值的功能,这自然是不行的了。
因此,重载拷贝构造函数
Myclass::Myclass(const Myclass& c) { a = c.a; }
此时就能运行了
E0349 没有与这些操作数匹配的 “<<” 运算符
关于流运算符为什么要写成$ostream& operator<<(ostream& os,const Myclass& d); 而非ostream& operator<<(ostream& os,Myclass& d);这个问题,网上绝大部分的回答都是输出没必要修改值。那么我们先定义后者
#head.h #pragma once #include<iostream> using std::ostream; class Myclass { private: int a; public: Myclass(int b=0):a(b) {} Myclass(Myclass& c); Myclass(const Myclass& c); ~Myclass(){} Myclass operator+(Myclass& d); friend ostream& operator<<(ostream& os ,Myclass& d); }; Myclass::Myclass(const Myclass& c) { a = c.a; } Myclass::Myclass(Myclass& c) { a = c.a; } Myclass Myclass::operator+(Myclass& d) { Myclass temp(d.a + a); return temp; } ostream& operator<<(ostream& os,Myclass& d) { os << d.a << std::endl; return os; } #main.cpp #include"head.h" int main() { Myclass a1(5); Myclass a2(12); Myclass&& sum = a1 + a2; std::cout << a1 + a2; //此处有讨厌小红线 }
不难发现,讨厌的小红线又出来了。
我们可以想象一下这个过程,a1.operator+(a2),返回了个临时变量,暂且假设它叫newguy,那么newguy为一个右值,又调用了函数os.<<(&d),传参为&d=newguy,现在问题来了,左值引用怎么能够接受一个纯右值呢? 而我们定义的重载的流运算符接受的参数类型为左值,我们并没有给出从左值到右值强制类型转换的函数,但是在上一部分,我们给出了从右值到左值的拷贝构造函数,因此,将流运算符声明为前者更好。
C3861 “function”: 找不到标识符
这个问题应该是非常常见的,不习惯将函数(或是类)先声明后定义而又喜欢让函数(或是类)相互调用,但是在类模板它比以上两种更为隐蔽。
#include<iostream> class A { friend void show(); //“声明”函数 friend void show1(); //“声明”函数 }; void show() //定义 { show1(); } void show1(){} //定义 int main() { A a; show(); //调用 show1(); //调用 }
以上流程看似声明->定义->调用非常完美,实则还是会报错的,不过跟以上两种不一样的是,它是在linking的时候出错,这是为什么呢?
原来友元函数并不属于这个类的一部分,在类内定义仅仅是为了告诉编译器“这个函数是这个类的友元函数”,并没有对这个函数本身进行声明,因此,正确的做法应该是这样的:
#include<iostream> void show(); void show1(); class A { friend void show(); friend void show1(); }; void show() { show1(); } void show1(){} int main() { A a; show(); show1(); }
总结
本文主要讲了三点。
首先,要注意将拷贝构造函数重载。
其次,要将流运算符<<的参数类型确定为(ostream&,const myclass&),当然,istream则万万不可const,ostream是没有拷贝构造函数的,因此引用也是必须的。
最后,类内友元函数的声明,并不等同于函数本身的声明。