对象析构谈—— delete this 的使用及注意事项
this对象是必须是用 new操作符分配的(而不是用new[],也不是用placement new,也不是局部对象,也不是global对象);
delete this后,不能访问该对象任何的成员变量及虚函数(delete this回收的是数据,这包括对象的数据成员以及vtable,不包括函数代码);
delete this后,不能再访问this指针。换句话说,你不能去检查它、将它和其他指针比较、和 NULL比较、打印它、转换它,以及其它的任何事情;
个人认为保证以上禁忌列表基本手段可以包括:
将析构函数私有化(如果有子类,则protected化,保证子类能够正确继承)--以保证对象必须使用new在堆上分配内存;
提供(可以在仅仅在基类中)Destroy(void)函数,里面仅有一句delete this--以保证第三方能够将分配的内存回收;
先说说为什么要转这篇文章,原因是这样的一种方式可以让我们很好的回收对象内存,怎么说呢。
比如你new了一个对象实例,然后呢他的使用者需要调用对象里的一个服务函数,想服务函数结束后就不再需要对象实例了,也就是说可以删除了,那么我们就可以通过在函数最后调用delete this将自己删除,也就是传说中的“自杀”。
但为什么能自杀呢,我个人简单的理解就是类成员函数是真实存在的,不管你实例是否存在,只不过如果没有实例的话,类成员函数中的this指针就不存在了,所以这也是平时我们不能随意调用类成员函数的原因,那么当我们调用delete this时,他删除了对象自己,但我们的程序依然跑在那个成员函数的代码中,只要delete this后面没有什么数据是依赖于this指针的话也就不会出现问题了。
当然这里有个例外的地方,就是不能在析构函数里调用delete this,否则就会进入死循环了。。。。。(这个你懂的,我不说了)
所以说知道了delete this的删除规则就可以很好的利用这个好东西了。
至于更具体的规则,你可以看下面的转载文章。
对象析构谈—— delete this 的使用及注意事项
In order to understand "delete this" :
First Step - dive into "delete p"
delete p 执行了哪些步骤?
delete p 是一个两步的过程:调用析构函数,然后释放内存。
delete p产生的代码看上去是这样的(假设是Object*类型的):
delete原语可以看作如下这样一个过程:
p->~Object();
p->operator delete(p);
p->~Object() 语句调用p指向的Object对象的析构函数。
p->operator delete(p) 语句调用对象p的内存释放原语 void operator delete(void* p)。如果没有实现该方法,将调用系统的内存释放原语::operator delete(ptr)做释放该对象内存的操作。当然细节上并不这么简单,我们最后的实验部分会详细讨论。
Second Step - "delete this"
成员函数调用delete this合法吗?
只要你小心,一个对象请求自杀(delete this),是可以的。
以下是我对“小心”的定义:
你必须100%的确定,this对象是用 new分配的(不是用new[],也不是用定位放置 new,也不是一个栈上的局部对象,也不是全局的,也不是另一个对象的成员,而是明白的普通的new)。
你必须100%的确定,该成员函数是this对象最后调用的的成员函数。
你必须100%的确定,剩下的成员函数(delete this之后的)不接触到this对象任何一块(包括调用任何其他成员函数或访问任何数据成员)。
你必须100%的确定,在delete this之后不再去访问this指针。换句话说,你不能去检查它,将它和其他指针比较,和NULL比较,打印它,转换它,对它做任何事。
自然,对于这种情况还要习惯性地告诫:当你的指针是一个指向基类类型的指针,而没有虚析构函数时(也不可以delete this)。
注意:因为是在类成员函数里面delete this的,所以在此语句以后,不能访问任何的成员变量及虚函数(调用虚函数必须对象实例存在以检查类型),否则一定非法。
上面所说的在执行时不一定会报错,但尽量不要这么做。
Some test examples:
析构函数本身是不会释放内存的,
除非在析构函数里面显示的使用delete操作符.
在对类指针使用delete时,实际发生了两个步骤。
A:先是调用该类的析构函数,以做数据成员的释放工作,以及一些finish code,这一切由程序员自己定义。
B:然后再调用operator delete(void*)释放该对象实例的内存数据。这是一个对象在消亡之前的所做的最后动作。一般不要override这个函数,如果要,务必记住最后调用系统的::operator delete真正释放该对象所占用的内存。
一般来说,内存释放释放的只能是数据段的内容(包括堆和栈,但释放栈上的内存由系统进行),而代码段的内存,除一些病毒攻击等非正常强行改写手段外,在内存中是永远不会释放/改变的,直到程序结束,因此在内存释放后也是可以访问的。所以,一般所谓的释放内存delete操作,是在数据段进行的释放。
可以试试下面的代码
Example 1: 两步操作
class x {
public :
x(){}
~x() {
printf("%s\n","~x()");
}
};
void main() {
x* p=new x;
::operator delete(p); //调用delete内存释放原语,不会调用~x(),如果确实调用了系统::operator delete,就没有内存泄露(也可能由用户函数覆盖)
delete p; //~x()依然会执行,operator delete中将会报错(最后将讨论)
}
Example 2: override重写的operator delete
class x {
public :
x(){
}
~x() {
printf("~x()\n");
//delete p; //这里若进行此操作则会陷入嵌套
}
void operator delete(void * ptr) {
printf("x::delete()\n");
}
};
void main() {
x* p=new x;
delete p; //依次调用p的~x()和operator delete
delete p; //不会报错,因为"operator delete" override了系统函数,没有进行::operator delete(this)操作。
delete p; //同理依然不会报错
}
Example 3: 默认的operator delete
class x {
public :
x(){
//delete p; //构造时delete不会报错,只要确保以后不会用到该实例(包括delete p)。
}
~x() {
printf("~x()\n");
}
};
void main() {
x* p=new x;
delete p; //依次调用p的~x()和operator delete(其中调用了系统的::operator delete)
//delete p; //报错,这里没有override,对象调用的是系统的::operator delete
}
进一步分析:
让我们看一下系统::operator delete的内部实现(in dbgdel.cpp):
void operator delete(
void *pUserData
)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK);
__TRY
pHead = pHdr(pUserData);
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)); //检查该段内存是否被程序占用。一般出现的如果内存已经释放了,又执行内存释放操作,这里就会报错
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK);
__END_TRY_FINALLY
return;
}
如果在里面设置断点,无论是直接调用::operator delete还是类似delete p调用对象的operator delete,如果没有人为override,都会进入这个函数,进行释放内存的操作。因此一个C++的类其实可以看做是有一个类似java的Object在内部进行操控:
class object {
public :
object() { }
~object() { }
void operator delete(void *ptr) {
::operator delete(ptr);
}
};
delete原语看起来会是如下的样子:
p->~object();
object::operator delete(p);
因为代码段的内存是不会被释放的,因此无论对象p的内存有没有释放,这两个语句都会执行,不会因为p没有指向任何存在的对象而报错,只是在最后执行到::operator delete的时候,才会在执行_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse))的时候报错。
Example 4: 代码段不会被释放
class x {
public :
x(){printf("x()\n"); }
~x() {
printf("~x()\n");
operator delete(this);
//以下并不会报错
int n=~get(34);//返回-6(~5)
x(); //会连带调用x()->~x()->operator delete
}
void operator delete(void *p) { //这其实是一个静态函数,成员函数调用不能放在里面
::operator delete(p);
printf("x::delete()\n");
}
int get(int ) { //注意这个申明是合法的
printf("x::get()\n");
return 5;
}
};
int main()
{
x* p=new x;
x::x(); //合法的,会连带调用x()->~x()->operator delete,但会因为具体对象不存在而报错
//p->x(); //调用非法,编译不通过
delete p;
int num = p->get(1); //不会报错,返回5
}
Example 5: 释放内存只释放数据段
class x {
public :
int n;
x() {
printf("x()\n");
n=5;
}
~x() {
printf("~x()\n");
}
void operator delete(void *p) { //这其实是一个静态函数,成员函数调用不能放在里面
::operator delete(p);
printf("x::delete()\n");
}
int get(int ) {
printf("x::get()\n");
return n;
}
void test() {
delete this;
//以下并不会报错
int n=this->get(34); //会得到一个非法值,不是5
}
};
int main()
{
x* p=new x;
p->test();
delete p;
int num = p->get(1); //会得到一个非法值,不是5
}
以上实验在VS 2005下测试通过
对象析构谈—— delete this 的使用及注意事项的更多相关文章
- 条目七《如果容器中包含了通过new操作创建的指针,切记在容器对象析构前将指针delete掉》
如果容器中包含了通过new操作创建的指针,切记在容器对象析构前将指针delete掉 在STL中容器是智能的,可以在容器销毁时自动调用容器里对象的析构函数来销毁容器存储的对象. STL的容器虽然比较智能 ...
- jsp内置对象浅谈
jsp内置对象浅谈 | 浏览:1184 | 更新:2013-12-11 16:01 JSP内置对象:我们在使用JSP进行页面编程时可以直接使用而不需自己创建的一些Web容器已为用户创建好的JSP内置对 ...
- stl 存放对象析构问题
vector内数据使用结构体的话是深拷贝,vector内的数据会拷贝一份保存,vector内数据不会丢失.如果vector内数据是指针的话是进行浅拷贝,数据超出作用域后会自动析构,vector内所指向 ...
- js delete删除对象属性,delete删除不了变量及原型链中的变量
js delete删除对象属性,delete删除不了变量及原型链中的变量 一.delete删除对象属性 function fun(){ this.name = 'gg'; } var obj = ne ...
- JS function 是函数也是对象, 浅谈原型链
JS function 是函数也是对象, 浅谈原型链 JS 唯一支持的继承方式是通过原型链继承, 理解好原型链非常重要, 我记录下我的理解 1. 前言 new 出来的实例有 _proto_ 属性, 并 ...
- 删除对象的属性 delete的用法
Javascript的变量 实际上JavaScript中,变量 = 对象属性,这是因为 Javascript 在执行脚本之前会创建一个Global对象,所有的全局变量都是这个Global对象的属性,执 ...
- 析构中delete this
查看下面代码如何出错 #include <iostream> using namespace std; class A { public: A() { p = this; } ~A() { ...
- javascript 删除对象的属性 delete
1.当属性存在 configurable:true delete命令会返回true var obj={a:1}; delete obj.a //true console.log(obj);//{} 2 ...
- 关于delete使用limit的一些注意事项
在使用delete删除记录时,如果表里面存在多条相同的记录,但是此刻你只想删除一条记录,那么limit就派上了用场.但是使用limit的时候得注意: 如图,您如果想着删除第一个名字叫做张三的,如果你这 ...
随机推荐
- Spark学习之路 (三)Spark之RDD[转]
RDD的概述 什么是RDD? RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变.可分区.里面的元素可并行计算的 ...
- 文件分配表(FAT)及其结构
原链接:https://blog.csdn.net/qianjintianguo/article/details/712590?utm_source=blogxgwz6 文件分配表(FAT)是文件管理 ...
- 牛客练习赛53 B题调和级数
https://ac.nowcoder.com/acm/contest/1114/B 这题时间卡的比较死,多了一个快速幂的logn就过不了这题. #include<bits/stdc++.h&g ...
- 2020牛客寒假算法基础集训营1 I-nico和niconiconi
#include <bits/stdc++.h> #define dbg(x) cout << #x << "=" << x < ...
- [P1361] 小M的作物 - 最小割
没想到今天早上的第一题网络流就血了这么多发 从经典的二选一问题上魔改 仍然考虑最小割 #include <bits/stdc++.h> using namespace std; #defi ...
- Three.js的开始(附代码)_2
1 下载Three.js代码 https://github.com/mrdoob/three.js/tree/master/build 2 引用方法 在HTML中添加以下代码: <script ...
- thinkphp中如何用路由调用前台html界面
先上图片看看基本的文件位置 1.首先在application\route.php中定义路由 <?php use think\Route; Route::get("home", ...
- Jupyter Notebook快捷键总结
1. Jupyter Notebook有两种mode Enter:进入edit模式 Esc:进入command模式 2. Command命令快捷键: A:在上方增加一个cell B:在下方增加一个ce ...
- 洛谷P1071 潜伏者
https://www.luogu.org/problem/P1071 #include<bits/stdc++.h> using namespace std; map<char,c ...
- Redis 数据结构的底层实现 (二) dict skiplist intset
一.REDIS_INCODING_HT (dict字典,hashtable) dict是一个用于维护key和value映射关系的数据结构.redis的一个database中所有的key到value的映 ...