成员访问运算符重载(详解)
目录
成员访问运算符
两层结构下的使用
三层结构下的使用(难点)
内存分析
成员访问运算符
成员访问运算符包括箭头访问运算符 -> 和解引用运算符 * ,它们是指针操作最常用的两个运算符。我们先来看箭头运算符 ->
箭头运算符只能以成员函数的形式重载,其返回值必须是一个指针或者重载了箭头运算符的对象。来看下例子:
两层结构下的使用
例子:建立一个双层的结构,MiddleLayer含有一个Data*型的数据成员
栈帧结束,栈帧结束,ml会调用析构函数,会delete _pdata 空间所以 ml 管理的对象至少应该在堆上的空间
Data*原生指针的用法如下,需要关注堆空间资源的回收
Data * p = new Data();
p->getData();
(*p).getData();
delete p;
p = nullptr;
如果用这种方式创建MiddleLayer对象,我们发现不需要手动delet pdata,并没有发生内存泄露,反而手动delet pdata后会有double free的问题
因为MiddleLayer对象实际上对堆上Data对象形成了接管。当函数栈帧结束会自动调用析构函数将堆上的空间回收,所以如果再次手动调用delete pdata 会出现double free的问题。
当然,也可以让Middlelayer对象自己管理一个Data对象,这样就不会出现那种情况了。
需求:希望实现一个这样的效果,创建MiddleLayer对象ml,让ml对象可以使用箭头运算符去调用Data类的成员函数getData
MiddleLayer ml(new Data);
cout << ml->getData() << endl;
箭头运算符无法应对MiddleLayer对象,那么可以定义箭头运算符重载函数。
-
首先不用考虑重载形式,箭头运算符必须以成员函数形式重载;
-
然后考虑返回类型,返回值需要使用箭头运算符调用getData函数,而原生的用法只有Data* 才能这么用,所以返回值应该是一个Data* ,此时应该直接返回 _pdata;
-
同时考虑到一个问题:MiddleLayer的数据成员是一个Data*,创建MiddleLayer对象时初始化这个指针,让其指向了堆上的Data对象,那么还应该补充析构函数使MiddleLayer对象销毁时能够回收这片堆上的资源。
Data* operator->(){return _pdata;
}
思考,解引用运算符应该如何重载能够实现同样的效果呢?直接使用MiddleLayer对象模仿Data*指针去访问getData函数
当我们完成了以上的需求后,还有一件“神奇”的事情,使用的语句中有new没有delete,但是检查发现并没有内存泄漏
原因:ml本身是一个局部对象,因为重载了箭头运算符和解引用运算符,所以看起来像个指针,也可以像指针一样进行使用,但是这个对象在栈帧结束时会自动销毁,自动调用析构函数回收了它的数据成员所申请的堆空间
实际上,这就是智能指针的雏形:其思想就是通过对象的生命周期来管理资源。
下面为测试代码,可以自行进行测试
#include<iostream>
using namespace std;
class Data {
public ://构造函数Data(){cout << "Data()" << endl;}Data(int x):_data(x){cout << "Data(int)" << endl;}int get()const { return _data; }~Data() { cout << "~Data()" << endl; }
private:int _data = 10;
};class middlelayer {
public:middlelayer(Data* p):_pdata(p){cout << "middlelayer(Data*)" << endl;}~middlelayer() {if (_pdata) {delete _pdata;_pdata = nullptr;}}Data * operator->(){return _pdata;}//要返回一个data对象,才可以用点来Data& operator* (){return *_pdata;}
private:Data* _pdata;
};void test()
{//下面两行代码会出错,因为栈帧结束,ml会调用析构函数,所以ml管理的对象至少应该在堆上的空间/*Data d1;middlelayer ml( &d1);*///不建议这样使用,//因为如果手动的通过p1指针来回收堆上的Data对象空间//会出现double free的问题Data* p1 = new Data();cout << p1->get() << endl;cout << (*p1).get() << endl;middlelayer ml(p1);//delete p1;
}
void test1()
{Data* p1 = new Data();cout << p1->get() << endl;cout << (*p1).get() << endl;delete p1;//智能指针的雏形//利用局部对象的生命周期管理堆上的资源//ml是middlelayer类对象,不是指针//但是可以像原生的data* 指针一样使用箭头运算符和解引用运算符//访问data的成员//并且,不需要像原生的指针一样去手动调用回收堆上的data空阿金middlelayer ml(new Data());//为什么可以进行重载呢,因为middlelayer底层的原型就是data类型cout << ml->get() << endl;//如下是middlelayer使用箭头运算符访问data类成员函数的本质形式//ml对象调用operator->函数,返回值是一个Data*//编译器自动加上了一个->原生的箭头运算符cout << ml.operator->()->get() << endl;cout << (*ml).get() << endl;cout << ml.operator*().get() << endl;//本质形式
}
int main()
{test1();return 0;
}
三层结构下的使用(难点)
-
拓展思考:那么如果结构再加一层,引入一个ThirdLayer类
-
创建ThirdLayer对象时注意避免这样的错误
注意:应该让ThirdLayer底层的指针管理一个堆上的MiddleLayer对象
希望实现如下使用方式,思考一下应该如何对ThirdLayer进行对应的运算符重载
ThirdLayer tl(new MiddleLayer(new Data));
cout << tl->getData() << endl;
cout << (*(*tl)).getData() << endl;
在ThirdLayer类中定义这两个成员函数
—— 在使用时就可以这样用
箭头运算符的使用:
解引用运算符的使用:
-
拓展思考:如果解引用的使用也希望和箭头运算符一样,一步到位,又该如何实现
ThirdLayer tl(new MiddleLayer(new Data));
cout << (*tl).getData() << endl;
如下三种return的形式都可以实现。
内存分析
三层的结构比较复杂,我们可以通过内存图的方式进行分析。
ThirdLayer对象的创建
ThirdLayer tl(new MiddleLayer(new Data));
实际上的内存结构如图
创建和销毁的过程:
创建tl对象时,调用ThirdLayer的构造函数,在ThirdLayer构造函数的参数初始化过程中调用MiddleLayer的构造函数,在ThirdLayer构造函数的参数初始化过程调用Data的构造。
Data构造完才能完成MiddleLayer的指针数据成员初始化,MiddleLayer创建完毕,才能完成ThirdLayer的指针数据成员初始化。
tl销毁时,马上调用ThirdLayer的析构,执行delete _pml时,第一步调用MiddleLayer的析构,在这个过程中,会delete _pdata,会调用Data的析构函数。
由于构造函数打印信息语句只能在函数体中,所以呈现出如下结果
下面为测试代码,可以自行测试
#include<iostream>
using namespace std;
class Data {
public ://构造函数Data(){cout << "Data()" << endl;}Data(int x):_data(x){cout << "Data(int)" << endl;}int get()const { return _data; }~Data() { cout << "~Data()" << endl; }
private:int _data = 10;
};class middlelayer {
public:middlelayer(Data* p):_pdata(p){cout << "middlelayer(Data*)" << endl;}~middlelayer() {cout << "~middlelayer()" << endl;if (_pdata) {delete _pdata;_pdata = nullptr;}}Data * operator->(){return _pdata;}//要返回一个data对象,才可以用点来Data& operator* (){return *_pdata;}friend class thirdlayer;
private:Data* _pdata;
};class thirdlayer {
public:thirdlayer(middlelayer* ml):_pml(ml){cout << "thirdlayer(middlelayer *)" << endl;}~thirdlayer() {cout << "~thirdlayer()" << endl;if (_pml){delete _pml;_pml = nullptr;}}//返回middlelayer对象,因为middlelayer对象对->进行了重载middlelayer& operator ->(){return *_pml;}//1.一步一步解引用/*middlelayer& operator*(){return *_pml;}*///2.一步到位解引用Data& operator*(){//从thirdlayer出发只能得到middlelayer对象//由于middlelayer对象已经对解引用运算符进行了初始化进行过重载//所以可以直接通过对middlelayer对象解引用得到data对象//return *(*_pml);//return (*_pml).operator*();//从内存的角度//_pml存储的是middlelayer,通过middlelayer访问data的地址,但是要将thirdlayer声明为middlelayer的友元类才能访问私有成员return *((*_pml)._pdata);}
private:middlelayer* _pml;
};void test()
{//下面两行代码会出错,因为栈帧结束,ml会调用析构函数,所以ml管理的对象至少应该在堆上的空间/*Data d1;middlelayer ml( &d1);*///不建议这样使用,//因为如果手动的通过p1指针来回收堆上的Data对象空间//会出现double free的问题Data* p1 = new Data();cout << p1->get() << endl;cout << (*p1).get() << endl;middlelayer ml(p1);//delete p1;
}
void test1()
{Data* p1 = new Data();cout << p1->get() << endl;cout << (*p1).get() << endl;delete p1;//智能指针的雏形//利用局部对象的生命周期管理堆上的资源//ml是middlelayer类对象,不是指针//但是可以像原生的data* 指针一样使用箭头运算符和解引用运算符//访问data的成员//并且,不需要像原生的指针一样去手动调用回收堆上的data空阿金middlelayer ml(new Data());//为什么可以进行重载呢,因为middlelayer底层的原型就是data类型cout << ml->get() << endl;//如下是middlelayer使用箭头运算符访问data类成员函数的本质形式//ml对象调用operator->函数,返回值是一个Data*//编译器自动加上了一个->(原生的箭头运算符)cout << ml.operator->()->get() << endl;cout << (*ml).get() << endl;cout << ml.operator*().get() << endl;//本质形式//是不可以的,和上面原因是一样的,因为tl是指向在栈上的空间,但是函数栈帧销毁的时候会调用delete,会出现错误//thirdlayer tl(&ml);}void test2()
{//调用tl的构造函数在调用middlelayer的构造函数在调用Data的构造函数,当data构造完成,后middlelayer构造完成,tl才能构造完成//就和递归很像thirdlayer tl(new middlelayer(new Data()));//tl调用operator->函数,返回值是其管理的middlelayer对象//由于middlelayer类中已经有对->运算符进行了重载//所以middlelayer对象可以直接使用->运算符访问data类中的成员//本质上就是调用了middlelayer的operator->函数cout << (tl.operator->()).operator->()->get() << endl;cout << (tl.operator->())->get() << endl;cout << tl->get() << endl;//1.一步一步解引用//因为middlelayer类已经对*运算符进行了重载//所以对third对象解引用只需要得到一个middlelayer对象/*cout << ((tl.operator*()).operator*()).get() << endl;cout << (*(*tl)).get() << endl;cout << (*(tl.operator*())).get() << endl;*///2.一步解引用cout << (*tl).get() << endl;cout << (tl.operator*()).get() << endl;
}
int main()
{test2();return 0;
}