C++手动调用析构函数无效问题排查

  在学习C++的时候,都知道不要手动调用析构函数,也不要在构造函数、析构函数里调用虚函数。工做这么多年,这些冷门的知识极少用到,渐渐被繁杂的业务逻辑淹没掉。html

  不过,最近项目里出现了析构函数没有被正确地调用,致使内存泄漏。代码大概以下:app

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在原来对象上从新建立一个对象也行。最后说一句,写代码,不是写得越复杂越高深才好,而是越通俗易懂越好,少用一些奇奇怪怪的写法用法。毕竟代码多数是须要维护的,公司招的人每一个人的水平都不同,通俗的代码则更容易维护。spa

相关文章
相关标签/搜索