一、文章来由

现在在写一个项目,需要用到多叉树存储结构,但是在某个时候,我需要销毁这棵树,这意味着如果我新建了一个树对象,我很可能在某处希望将这个对象的声明周期终结,自然会想到显示调用析构函数,但是就扯出来这么大个陷阱。

二、原因

在了解为什么不要轻易显示调用析构函数之前,先来看看预备知识。
为了理解这个问题,我们必须首先弄明白“堆”和“栈”的概念。

1)堆区(heap) —— 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

2)栈区(stack) —— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

我们构造对象,往往都是在一段语句体中,比如函数,判断,循环,还有就直接被一对“{}”包含的语句体。这个对象在语句体中被创建,在语句体结束的时候被销毁。问题就在于,这样的对象在生命周期中是存在于栈上的。也就是说,如何管理,是系统完成而程序员不能控制的。所以,即使我们调用了析构,在对象生命周期结束后,系统仍然会再调用一次析构函数,将其在栈上销毁,实现真正的析构。

所以,如果我们在析构函数中有清除堆数据的语句,调用两次意味着第二次会试图清理已经被清理过了的,根本不再存在的数据!这是件会导致运行时错误的问题,并且在编译的时候不会告诉你!

三、显示调用带来的后果

如果硬要显示调用析构函数,不是不可以,但是会有如下3条后果:

1)显式调用的时候,析构函数相当于的一个普通的成员函数

2)编译器隐式调用析构函数,如分配了对内存,显式调用析构的话引起重复释放堆内存的异常

3)把一个对象看作占用了部分栈内存,占用了部分堆内存(如果申请了的话),这样便于理解这个问题,系统隐式调用析构函数的时候,会加入释放栈内存的动作(而堆内存则由用户手工的释放);用户显式调用析构函数的时候,只是单纯执行析构函数内的语句,不会释放栈内存,也不会摧毁对象

用如下代码表示:

例1:

class aaa
{
public:aaa(){}~aaa(){cout<<"deconstructor"<<endl; } //析构函数void disp(){cout<<"disp"<<endl;}
private:char *p;
};void main()
{
aaa a;
a.~aaa();
a.disp();
}

分析:

这样的话,显式两次destructor,第一次析构相当于调用一个普通的成员函数,执行函数内语句,显示第二次析构是编译器隐式的调用,增加了释放栈内存的动作,这个类未申请堆内存,所以对象干净地摧毁了,显式+对象摧毁

例2:

class aaa
{
public:aaa(){p = new char[1024];} //申请堆内存~aaa(){cout<<"deconstructor"<<endl; delete []p;}void disp(){cout<<"disp"<<endl;}
private:char *p;
};void main()
{
aaa a;
a.~aaa();
a.disp();
} 

分析:

这样的话,第一次显式调用析构函数,相当于调用一个普通成员函数,执行函数语句,释放了堆内存,但是并未释放栈内存,对象还存在(但已残缺,存在不安全因素);第二次调用析构函数,再次释放堆内存(此时报异常),然后释放栈内存,对象销毁

四、奇葩的错误

系统在什么情况下不会自动调用析构函数呢?显然,如果对象被建立在堆上,系统就不会自动调用。一个常见的例子是new…delete组合。但是好在调用delete的时候,析构函数还是被自动调用了。很罕见的例外在于使用布局new的时候,在delete设置的缓存之前,需要显式调用的析构函数,这实在是很少见的情况。

我在栈上建树之后,显示调用析构函数,对象地址任然存在,甚至还可以往里面插入节点。。。

其实析构之前最好先看看堆上的数据是不是已经被释放过了。

////////////////a.hpp
#ifndef A_HPP
#define A_HPP#include <iostream>
using namespace std;class A
{
private:int a;int* temp;bool heap_deleted;
public:A(int _a);A(const A& _a);~A();void change(int x);void show() const;
};#endif////////////a.cpp#include "a.hpp"
A::A(int _a): heap_deleted(false)
{temp = new int;*temp = _a;a = *temp;cout<< "A Constructor!" << endl;
}A::A(const A& _a): heap_deleted(false)
{temp = new int;*temp = _a.a;a = *temp;cout << "A Copy Constructor" << endl;
}A::~A()
{if ( heap_deleted == false){cout << "temp at: " << temp << endl;delete temp;heap_deleted = true;cout << "Heap Deleted!\n";}else {cout << "Heap  already Deleted!\n";}cout << "A Destroyed!" << endl;
}void A::change(int x)
{a = x;
}void A::show() const
{cout << "a = " << a << endl;
}//////////////main.cpp#include "a.hpp"
int main(int argc, char* argv[])
{A a(1);a.~A();a.show();cout << "main() end\n";a.change(2);a.show();return 0;
}

五、小结

所以,一般不要自作聪明的去显示调用析构函数。

—END—


参考文献

[1] http://blog.csdn.net/todototry/article/details/1483614
[2] http://club.topsage.com/thread-2228024-1-1.html

关于c++显示调用析构函数的陷阱相关推荐

  1. C++显示调用析构函数

    C++中new的用法及显示调用析构函数 最近被问到了C++内存池的问题,其中不免涉及到在指定内存地址调用对象构造函数以及显示调用对象析构函数的情况. C++中new的用法 new是C++中用于动态内存 ...

  2. C++ 类析构函数的显示调用和隐式调用

    类的析构函数调用方式 堆和栈 结论 系统在什么情况下不会自动调用析构函数呢? 举例 参考与致谢 堆和栈 为了理解这个问题,我们必须首先弄明白"堆"和"栈"的概念 ...

  3. C++主动调用析构函数分析

    1.  关于主动调用析构函数: C++编程规范中都不支持显示的调用析构函数,部分文章中甚至说明析构函数是不能显示调用的,然而执行如下类似的操作编译器并不会报错,而且会调用成功. pa->~A() ...

  4. C++入门经典-例8.3-子类显示调用父类构造函数

    1:当父类含有带参数的构造函数时,创建子类的时候会调用它吗?答案是通过显示方式才可以调用. 无论创建子类对象时调用的是那种子类构造函数,都会自动调用父类默认构造函数.若想使用父类带参数的构造函数,则需 ...

  5. 23.C++- 继承的多种方式、显示调用父类构造函数、父子之间的同名函数、virtual虚函数...

     上章链接: 22.C++- 继承与组合,protected访问级别 继承方式 继承方式位于定义子类的":"后面,比如: class Line : public Object // ...

  6. C++ 容器存储对象时,指针调用析构函数触发的一系列BUG

    今天给大家分享一篇BUG文章,请耐心看完,也许你以后也会遇到这样的BUG而解决不了! 需求是这样的: 定义一个Student类,里面有私有成员整型和指针! 例如:int age; char *name ...

  7. 是否可以手动调用析构函数

    答案是可以. 但是有个误区要注意.虽然可以手动调用析构函数,但是对象的内存并没有释放.看一下代码示例: #include "stdafx.h" #include <iostr ...

  8. python在删除对象时会自动调用析构函数_Python面向对象程序设计构造函数和析构函数用法分析...

    本文实例讲述了Python面向对象程序设计构造函数和析构函数用法.分享给大家供大家参考,具体如下: 构造函数和析构函数 1.构造方法的使用 很多类都倾向于将对象创建为有初始化状态.因此类可以定义一个名 ...

  9. Linux 动态库的显示调用

    Linux 动态库的显示调用 分类: 动态库与静态库 2012-03-17 23:56 1710人阅读 评论(0) 收藏 举报 linuxnulllibrary测试web服务apache 10.动态库 ...

最新文章

  1. 图像复原的神经网络稀疏表示
  2. 新一代平板电脑 三星Galaxy Note 10.1将于本月末发布 - TECH2IPO创见
  3. spring mvc 自动生成代码
  4. Spring陷阱:代理
  5. 如何在linux下创建一个可运行shell脚本?
  6. “一张图”解释特色小镇发展历程
  7. struct与class的区别
  8. 为什么++ [[]] [+ []] + [+ []]返回字符串“ 10”?
  9. yarn 安装使用小记
  10. 什么是微网格?微网格规划应考虑哪些因素?
  11. 利用js+html做一个简单的体脂率计算
  12. namespace is terminated
  13. 为什么苹果允许用户安装未受信任的企业级开发者所开发的软件?
  14. Windows 10 支持AAC编码的方法
  15. 一文读懂HTTP, HTTPS, SSL和TLS
  16. flutter之路由管理
  17. 利用Python进行股票交易分析(三):A股量化交易策略的验证及数据分析。
  18. iOS KVC和KVO
  19. netstat,ss,nc ,wget,dig
  20. 【高性能计算背景】《并行计算教程简介》翻译 - 中文 - 3 / 4

热门文章

  1. 一位高人解答的关于开启“数据漫游”才能上网的解答
  2. 用于《机械设计课程设计》中的减速箱设计的Python计算程序
  3. 2021/06/29计算机视觉期末复习笔记整理
  4. ffmpeg从视频中提取图片
  5. 小米路由器青春版装linux,小米路由器青春版开启SSH
  6. 高新技术企业认定的好处
  7. 戴雪儿, 泡泡糖 戴雪儿,戴雪儿资料,戴雪儿mv,戴雪儿mtv
  8. 通风橱尺寸及通风量(通风橱排风要求)
  9. 外卖点餐 堂食点餐 扫码点餐源码 点餐APP源码
  10. 粉丝福利 | Ceph 亚太峰会来袭,开源云中文社区带你去!