C++手动调用析构函数无效问题排查
在学习C++的时候,都知道不要手动调用析构函数,也不要在构造函数、析构函数里调用虚函数。工作这么多年,这些冷门的知识极少用到,渐渐被繁杂的业务逻辑淹没掉。
不过,最近项目里出现了析构函数没有被正确地调用,导致内存泄漏。代码大概如下:
class base_mem_alloc
{
public:
base_mem_alloc() {}
virtual ~base_mem_alloc() {}
}; class mem_alloc : public base_mem_alloc
{
public:
~mem_alloc() {}
virtual ~mem_alloc()
{
// 释放内存
}
}; class test_conf
{
public:
bool reload();
private:
class mem_alloc _alloc;
} bool test_conf::reload()
{
// 正常写法,释放所有内存,但没有这个接口
// _alloc.release() // 有问题的写法
class mem_alloc tmp;
_alloc.~mem_alloc(); // 通过析构函数释放内存
_alloc = tmp; // 通过拷贝构造函数将内部变量初始化 // 使用_alloc分配内存
}
公司的框架要求使用统一的内存分配器。像读取配置这种逻辑,在配置不需要的时候(也就是关掉进程)是直接从分配器统一释放的,但这框架有点年代了,之前没有考虑游戏热更配置的问题。现在要求能重新加载配置,那么就少了一个释放内存的接口。于是,便通过析构函数释放内存,然后再用拷贝构造函数把一个临时分配器拷贝过来。虽然这种写法极其少见,但咋一看,好像也没问题。然后不幸的是,这种写法真的有问题,~mem_alloc()这个析构函数是无法正常调用的。
代码中的内存分配器用了多态,C++的多态是依赖虚函数表实现的,虚函数表是在构造函数的时候一步步创建,在析构函数一步步销毁。之所以说是一步步,因为C++在调用构造函数时,会从基类构造函数--子类构造函数构造,析构时从子类析构函数--基类析构函数。在这个过程中,对象的类型也是会变的,调用基类构造函数的时候,他的类型就是基类,调用子类构造函数时,就是子类。析构时则反过来,所以析构完成后,对象的类型是基类(理论上讲,不再存在这个对象,但他的数据遗留是基类)。参考:https://www.artima.com/cppsource/nevercall.html
During base class construction of a derived class object, the type of the object
is that of the base class. Not only do virtual functions resolve to the base class,
but the parts of the language using runtime type information (e.g., dynamic_cast (see Item 27)
and typeid) treat the object as a base class type.An object doesn't become a derived class object
until execution of a derived class constructor begins. The same reasoning applies during destruction. Once a derived class destructor has run,
the object's derived class data members assume undefined values, so C++ treats them as if
they no longer exist. Upon entry to the base class destructor, the object becomes a base class
object, and all parts of C++—virtual functions, dynamic_casts, etc.—treat it that way.
由于虚函数表在构造、析构过程中是变化的,因此在这时调用虚函数可能不会得到正确的结果。而像dynamic_cast这种依赖运行时的转换,也不可用。上面出问题的代码中,手动调用析构函数,第一次是能够正常调用的,然后就变成了基类。在使用拷贝构造函数时,会拷贝临时对象的数据,但是并不会重建虚函数表。由于在我们项目的代码中大部分功能是由基类完成的,使用拷贝构造函数后,对象的内存数据也没有被破坏。因此运行起来并没有什么太大的问题,再加上这个地方是需要多次重新加载配置才能重现,导致这个问题被隐藏了一段时间。
其实这个问题很好解决。加个释放函数就OK,或者换用指针,delete掉再new就可以了。用placement new在原来对象上重新创建一个对象也行。最后说一句,写代码,不是写得越复杂越高深才好,而是越通俗易懂越好,少用一些奇奇怪怪的写法用法。毕竟代码多数是需要维护的,公司招的人每个人的水平都不一样,通俗的代码则更容易维护。
C++手动调用析构函数无效问题排查的更多相关文章
- 编写SqlHelper使用,在将ExecuteReader方法封装进而读取数据库中的数据时会产生Additional information: 阅读器关闭时尝试调用 Read 无效问题,解决方法与解释
在自学杨中科老师的视频教学时,拓展编写SqlHelper使用,在将ExecuteReader方法封装进而读取数据库中的数据时 会产生Additional information: 阅读器关闭时尝试调用 ...
- 【Win 10应用开发】手动调用WCF服务
调用服务最简单的方法就是,直接在VS里面添加服务引用,输入服务的地址即可,无论是普通Web服务,还是WCF服务均可.VS会根据获取到的元数据,自动生成客户端代码. 如果服务的调用量很大,应用广泛,可以 ...
- 由STL所想到的 C++显示调用析构函数
在STL中的容器中的析构函数中,会经常调用destroy()这个函数 在STL中 destroy()被重载了 这点在这里到不去讨论 这里贴最简单的那个版本 template<class T&g ...
- Android 关于ListView中adapter调用notifyDataSetChanged无效的原因
话说这个问题已经困扰我很久了,一直找不到原因,我以为只要数据变了,调用adapter的notifyDataSetChanged就会更新列表,最近在做微博帐号管理这一块,想着动态更新列表,数据是变了,但 ...
- Android开发之关于ListView中adapter调用notifyDataSetChanged无效的原因
1.数据源没有更新,调用notifyDataSetChanged无效. 2.数据源更新了,但是它指向新的引用,调用notifyDataSetChanged无效. 3.数据源更新了,但是adpter没有 ...
- SherlockactionBar中手动调用onCreateOptionsMenu的办法
我们有时候要做做事的时候,要提前拿到 ActionBar中的menuItem,但是,会出现为空的情况.怎么办呢? 比如这里: @Override public boolean onCreateOpti ...
- 手动调用run方法和普通方法调用没有区别
手动调用run方法和普通方法调用没有区别
- Android 手动调用 返回键
有人想通过下面代码来实现手动调用返回键,很可惜会出现空指针异常. this.onKeyDown(KeyEvent.KEYCODE_BACK, null); 我们可以通过调用 onBackPressed ...
- 关于c++显示调用析构函数的陷阱
版权声明:欢迎转载,注明出处就好!如果不喜欢请留言说明原因再踩哦,谢谢,我也可以知道原因,不断进步!! 目录(?)[+] 一.文章来由 现在在写一个项目,需要用到多叉树存储结构,但是在某个时候 ...
随机推荐
- 阶段5 3.微服务项目【学成在线】_day03 CMS页面管理开发_14-异常处理-异常处理的问题分析
这块代码没有异常处理.如果在Service出现了异常代码,在哪里捕获?要么在Servive内捕获,要么在调用service的地方也就是controller内捕获 每个调用service的地方都要去捕获 ...
- 阶段5 3.微服务项目【学成在线】_day03 CMS页面管理开发_03-自定义查询页面-前端
下拉选择框 <!--查询表单--> <el-form :model="params"> <el-select v-model="params ...
- 安装php扩展sphinx-1.2.0.tgz和libsphinxclient0.9.9
一.首先安装libsphinxclient(php模块需要) cd /usr/local/src/tar zxvf sphinx-0.9.9.tar.gzcd sphinx-0.9.9/api/lib ...
- 【计算机视觉】MTCNN的windows-cpu配置
前言 MTCNN是级联卷积网络,原理基本上比较清晰,只是还缺少实战,看到一个CSDN上windows的实现过程,就拿来试试. 操作过程 参考here,某些步骤会添加博主遇到的问题的解释. 第一部分:c ...
- Android学习笔记之View
转载: 0.7562018.10.22 21:44:10字数 5,423阅读 189 导图 一.View事件体系 1.什么是 View 和 View的位置坐标 View是什么: View 是一种界 ...
- vim复制粘贴导致多行出现#号解决办法
在vim内复制多行假如复制的行带有#号会导致其他不带#号的行自动加# 解决办法,输入一下命令再粘贴即可 :set paste
- DELL服务器管理工具和RACADM介绍
DELL服务器管理工具和RACADM介绍 一.Dell服务器管理工具介绍 Dell对服务器(DELL PowerEdge)的管理主要提供了三种管理工具,分别是Dell Remote Access Co ...
- Outlook2016中如何实现自动密送
Outlook2016中如何实现自动密送 下面的方案您可以参考一下: 1)在Outlook里面键入Alt+F11打开VBA编辑器: 2)激活左边的工程面板,展开并双击上面的“Project (VbaP ...
- .Netcore 2.0 Ocelot Api网关教程(10)- Headers Transformation
本文介绍Ocelot中的请求头传递(Headers Transformation),其可以改变上游request传递给下游/下游response传递给上游的header. 1.修改ValuesCont ...
- List的add方法与addAll方法的区别、StringBuffer的delete方法与deleteCharAt的区别
List的add方法与addAll方法 区别 add add是将传入的参数作为当前List中的一个Item存储,即使你传入一个List也只会另当前的List增加1个元素 addAll addAll是传 ...