一.析构函数是什么?
析构函数(destructor) 与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作。
例如,在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存。
析构函数有如下特点:
1. 构函数名与类名相同,但在前面加上字符‘~’。
2. 析构函数无函数返回类型,与构造函数在这方面是一样的。但析构函数不带任何参数。
3. 一个类有一个也只有一个析构函数,这与构造函数不同。析构函数可以缺省。
4. 对象注销时,系统自动调用析构函数。
格式如下:
class 类名
{
~类名()
{
...
}
}
类名::~类名()
{
...
}
例如,如下定义是合法的:
class CStudent
{
~CStudent();
}
CStudent::~CStudent()
{
...
}
二.析构函数有什么作用?
下面用一个实例代码说明析构函数的作用。
#include<iostream>
using namespace std;
class Test
{
public:
Test();
~Test();
private:
int *age;
};
Test::Test()
{
this->age = new int(0);
}
Test::~Test()
{
delete age;
cout<<"Destructor"<<endl;
}
int main()
{
Test *t = new Test();
delete t;
};
运行结果如下图。析构函数被自动调用。
三.析构函数如何实现?
3.1 缺省析构函数
如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数。
其形式如下:
Test::~Test()
{
}
3.2 自定义析构函数
如果用户自定义了析构函数,编译器在执行时会调用自定义的析构函数。
四.析构函数的汇编代码分析
4.1 代码示例
#include<iostream>
using namespace std;
class Test
{
public:
Test();
~Test();
private:
int age;
};
Test::Test()
{
this->age = 0;
}
Test::~Test()
{
cout<<"Destructor"<<endl;
}
int main()
{
Test t;
return 0;
};
4.2 main函数的汇编代码
<+0>: push %rbp
<+1>: push %rbx
<+2>: sub $0x38,%rsp
<+6>: lea 0x80(%rsp),%rbp
<+14>: callq 0x40e790 <__main>
<+19>: lea -0x60(%rbp),%rax //取栈偏移60的地址传送给rax,实质是对象t的地址
<+23>: mov %rax,%rcx //对象t的地址传给rcx,实质就是this指针
<+26>: callq 0x401530 <Test::Test()>//构造函数
<+31>: mov $0x0,%ebx
<+36>: lea -0x60(%rbp),%rax//取栈偏移60的地址传送给rax,实质是对象t的地址
<+40>: mov %rax,%rcx//对象t的地址传给rcx,实质就是this指针
<+43>: callq 0x401544 <Test::~Test()>//析构函数
<+48>: mov %ebx,%eax
<+50>: add $0x38,%rsp
<+54>: pop %rbx
<+55>: pop %rbp
<+56>: retq
上面代码中:
(1)在main函数的栈中分配了一个空间给对象t,并通过rcx传送给构造函数
如下几行代码中,取栈偏移60的地址传送给rax,实质是对象t的地址;对象t的地址传给rcx,实质就是this指针;通过rcx传给构造函数,这就是实现隐藏的this指针功能。
<+19>: lea -0x60(%rbp),%rax //取栈偏移60的地址传送给rax,实质是对象t的地址
<+23>: mov %rax,%rcx //对象t的地址传给rcx,实质就是this指针
<+26>: callq 0x401530 <Test::Test()>//构造函数
(2)对象t的地址通过rcx传送给析构函数
如下几行代码中,取栈偏移60的地址传送给rax,实质是对象t的地址;对象t的地址传给rcx,实质就是this指针;通过rcx传给析构函数,这也是实现隐藏的this指针功能。
<+36>: lea -0x60(%rbp),%rax//取栈偏移60的地址传送给rax,实质是对象t的地址
<+40>: mov %rax,%rcx//对象t的地址传给rcx,实质就是this指针
<+43>: callq 0x401544 <Test::~Test()>//析构函数
4.3 析构函数的汇编代码
析构函数“Test::~Test()”的汇编代码如下:
<+0>: push %rbp
<+1>: mov %rsp,%rbp
<+4>: sub $0x20,%rsp
<+8>: mov %rcx,0x10(%rbp)//将对象t的地址(即this指针)压入栈中
<+12>: mov 0x10(%rbp),%rax//对象t的地址传给rax
<+16>: mov (%rax),%rax
<+19>: mov %rax,%rcx
<+22>: callq 0x470900 <_ZdlPv>
<+27>: lea 0x86a84(%rip),%rdx # 0x488000
<+34>: mov 0x8b10d(%rip),%rcx # 0x48c690 <.refptr._ZSt4cout>
<+41>: callq 0x46eca0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
<+46>: mov 0x8b111(%rip),%rdx # 0x48c6a0 <.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_>
<+53>: mov %rax,%rcx
<+56>: callq 0x44d390 <_ZNSolsEPFRSoS_E>
<+61>: nop
<+62>: add $0x20,%rsp
<+66>: pop %rbp
<+67>: retq
析构函数比较简单,它通过rcx寄存器获得对象t的地址,即this指针。
五.应用实例
5.1 代码
下面结合构造函数,举一个简单的实际应用例子。
#include <string.h>
#include <iostream>
using namespace std;
class CStudent
{
public:
//构造函数
CStudent(const char *name,char sex,int score);
void display(void);
//析构函数
~CStudent();
private:
char name[50];
char sex;
int score;
};
CStudent::CStudent(const char *name,char sex,int score)
{
strcpy(this->name, name);
this->sex = sex;
this->score = score;
}
void CStudent::display(void)
{
cout<<"name : "<<name<<endl;
cout<<"sex : "<<sex<<endl;
cout<<"score: "<<score<<endl;
}
CStudent::~CStudent() //析构函数
{
cout<<"Destructor!"<<endl;
}
int main()
{
CStudent stu1("Jony",'M', 80);
CStudent stu2("Lily", 'W',90);
stu1.display();
stu2.display();
return 0;
}