在开始本文之前,我假设你已经具备了C和C++的知识,对面向对象有所了解
前几天和同学研究了一下delete this,因为以前在vs 2005 上试过delete this,编译通过,运行报错,我当时心安理得的认为不存在这种用法(孤陋寡闻啊~惭愧~~)。
直到今天,一个很偶然的机会,我发现如果利用指向对象的指针来调用其中的函数,是可以delete this的,没有内存泄漏问题(除非你故意要把同一段内存多次释放)。
这里要声明一下,注意了,如果是通过产生对象的实例来调用函数,那么你的delete this会出错,这个有必要解释一下,刚开始以为应该是编译器的问题,后来慢慢想了想明白了些,说的不对的,还请教高手。
其实在delete之前,一定要有new,但是直接从类实例化一个对象,并没有这样,new操作符是调用了构造函数,而直接实例化没有通过new而是直接调用构造函数,
因为之前没有new,所以在delete的时候会出错(不是内存泄漏),原因看完下文就明白了。
接着我研究了一下this指针,发现很多以前误解的地方,但是我没看过几本书,我更倾向于自己编写代码来帮助思考,而不太喜欢翻别人的书或者找资料(绝对的坏习惯)。
这样的代价是思考容易片面,时间代价高,好处是,思考更加独立,理解比较透彻和深刻。
所以……(汇编老师习惯性的抽了下口水——咝,我跟你们说,咝,要多读点书,咝,将来……)
下面进入正题,一开始我认为this指针是对象的成员(但它拥有当前对象的控制权),那么在成员函数的内部不可以释放自身,这个可以实验下,你可以尝试在成员函数内显式的调用析构函数,其实该对象没有被释放掉(至少在vs2005、vs2008上是这样的),因此更坚定我的错误观点。
很多人知道,执行delete this的时候,其实调用了类的析构函数,我设想,如果是这样,那么类的结构应该类似下面这样(纯粹臆想,并无研究过C++如何从C演化来)
struct X
{
int _x;
void ( *pFunc )( struct X* &_pX );
};
void Release( struct X* &_pX )
{
if( _pX )
delete _pX;
//_pX = NULL;
}
void main()
{
X* pX = new X;
pX->_x = 0;
pX->pFunc = Release;
pX->pFunc( pX ); // pX被释放,这个地方是不是看起来很像调用成员函数?这个是产生猜想的源头。
X* _pX = new X;
pX->pFunc( _pX ); // 出错,此时pFunc已经被释放,与我们预期的一样
}
// 如果是这样,那么当对象被释放之后,应该无法调用其中的函数,真是这样吗?下面继续代码分析
///////////////////////////////////////////////////////////////////
// 类声明
class X
{
public:
X(); // Constructor
public:
void Func();
void _Func();
virtual void VFunc();
virtual void _VFunc();
private:
int _x;
};
// 实现
X::X():_x( 0 )
{
}
void X::Func()
{
if( this )
delete this;
}
void X::_Func()
{
cout<<"Call the _Func() !"<<endl;
}
void X::VFunc()
{
if( this )
delete this;
}
void X::_VFunc()
{
cout<<"Call the _VFunc() !"<<endl;
}
//////////////////////////////////////////////////////////////////////////
在单步调试的时候发现,this中的一些微妙的细节,里面包含了数据域(预料中),还有一个__vfptr指针,
这个指针指向一段连续的空间(一维数组),它的长度为虚函数的个数(每增加一个虚函数,长度就增加一),
那么我们有理由相信this其实包含了数据域,还有所有的虚函数指针。
现在我们通过代码来验证我们的想法
void main()
{
X* pX = new X;
pX->Func();
pX->VFunc(); // 出错,此时pX已经被释放,那么其中的虚函数指针同样被释放
// 这个验证我们猜想的一部分this包含了虚函数指针,或者说虚函数指针属于this指向的对象
// 它与对象的生存周期一致
//pX->_VFunc(); // 这个同样不行
}
接下来的问题,this是否是类的成员?如果不是的话它又是什么?
void main()
{
X* pX = new X;
pX->Func();
pX->Func(); // 出错,this指向的对象(实例)确实被释放了,但是this本身并没有更改,为何?
// 难道this不是在X生成的对象中吗?对象析构之后,this应该被释放?其实,this此时与pX占用的是同一段内存!
// 可以这样理解,this == const pX,this指向的对象可以被改变(释放),然而无法改变this的指向。
// this不是X的数据成员,但是却可以绑定在X产生的实例上,也正是这样,this可以代理当前对象,而对象被释放的时候它仍然存在。
// pX在此并没有被释放(只是它指向的对象被释放),也即pX != NULL 因此 this != NULL,函数中的delete this被执行,
// 显然同一段内存被释放两次,必定会出错。
// 在这里我们注意到,即使pX指向的对象已经被释放,但是pX->Func()仍然可以调用,是这样吗?
}
为了进一步验证我们的想法,再来
void main()
{
X* pX = new X;
pX->Func();
pX = NULL;
pX->Func(); // 运行结果正确,显然pX没有指向一个实例,但是仍然可以调用成员函数!!
// 而且delete this不会被执行(this == pX == NULL)
// 那么现在可以说,成员函数的调用与this的指向无关
}
如果函数不涉及对this的操作,也不涉及对对象的操作,情况如何?
void main()
{
X* pX = new X;
pX->Func();
pX->_Func();
}
// 此时执行的结果是正确的,_Func此时不涉及对this的操作,也不涉及对对象的操作
// 也就是说this不包含除虚函数外的其他成员函数,当然肯定也不包含静态成员函数,
// 这个比较容易理解,静态成员函数不属于任何一个对象,在静态成员函数内部不能使用this指针就是一个很好的证明
this和对象指针在这里可以认为是等同的,所以我们通过观察对象指针进一步观察this。
综上所述:
this指针是在类的内部代理指向对象的指针(但是加了限定,不能改变this的指向,否则外部的对象指针也会改动,显然会产生无法预期的结果),
它只包含对象中的数据域以及虚函数指针,只要产生一个指向类的对象的指针,那么this就存在了,this与类的对象是否存在无关。
类的对象一经产生,必定占用一段内存,那么该内存就被一个指针所指向,也即,产生对象,必定产生指针,有指针,就有this。
这里有几个非常重要的细节需要注意:
1.当指向对象的指针并没有真正指向一个有效的对象的时候(为NULL,或指向的对象已经被释放),那么其中的所有虚函数无法被正常调用(编译通过,运行报错)
2.无论指向对象的指针指向何处,对非静态成员函数的调用都是有效的(前提是函数中不涉及this指针和数据域),有时候可以有意利用这一点。
delete this——引发的思考
最新推荐文章于 2023-05-04 22:57:31 发布