delete this——引发的思考

本文探讨了C++中this指针的概念与行为,通过实验分析了this指针与对象的关系,及其在成员函数调用过程中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在开始本文之前,我假设你已经具备了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指针和数据域),有时候可以有意利用这一点。

access-list mac Description The access-list mac command is used to create MAC ACL. To delete the MAC ACL, please use no access-list mac. Syntax access-list mac acl-id-or-name rule { auto | rule-id } { deny | permit } logging {enable | disable} [smac source-mac smask source-mac-mask ] [dmac destination-mac dmask destination-mac-mask ] [type ether-type] [pri dot1p-priority] [vid vlan-id] [tseg time-range-name] no access-list mac acl-id-or-name rule rule-id Parameter acl-id-or-name —— Enter the ID or name of the ACL that you want to add a rule for. auto —— The rule ID will be assigned automatically and the interval between rule IDs is 5. rule-id —— Assign an ID to the rule. deny | permit —— Specify the action to be taken with the packets that match the rule. By default, it is set to permit. The packets will be discarded if “deny” is selected and forwarded if “permit” is selected. enable | disable —— Enable or disable Logging function for the ACL rule. If "enable " is selected, the times that the rule is matched will be logged every 5 minutes. With ACL Counter trap enabled, a related trap will be generated if the matching times changes. source-mac —— Enter the source MAC address. The format is FF:FF:FF:FF:FF:FF. 410 source-mac-mask —— Enter the mask of the source MAC address. This is required if a source MAC address is entered. The format is FF:FF:FF:FF:FF:FF. destination-mac —— Enter the destination MAC address. The format is FF:FF:FF:FF:FF:FF. destination-mac-mask —— Enter the mask of the destination MAC address. This is required if a destination MAC address is entered. The format is FF:FF:FF:FF:FF:FF. ether-type —— Specify an Ethernet-type with 4 hexadecimal numbers. dot1p-priority: The user priority ranges from 0 to 7. The default is No Limit. vlan-id —— The VLAN ID ranges from 1 to 4094. time-range-name —— The name of the time-range. The default is No Limit. Command Mode Global Configuration Mode Privilege Requirement Only Admin, Operator and Power User level users have access to these commands. Example Create MAC ACL 50 and configure Rule 5 to permit packets with source MAC address 00:34:a2:d4:34:b5: Switch (config)#access-list create 50 Switch (config-mac-acl)#access-list mac 50 rule 5 permit logging disable smac 00:34:a2:d4:34:b5 smask ff:ff:ff:ff:ff:ff
最新发布
08-20
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值