文档章节

C++ 析构函数与内存池

YLMe
 YLMe
发布于 2017/07/18 21:07
字数 1367
阅读 32
收藏 0

C++ Primer 书中也提到编写 class 时要注意 copy control 成员(拷贝构造函数,赋值操作符,析构函数, C++11 又多个移动构造函数)。工作时在 C++ 和 C# 之间切换,有时就忘记了 C++ 的细节(真的好讨厌)。 C++ 析构函数与构造函数对应,构造对象时调用构造函数,析构对象时调用析构函数,于是可以在对象的析构函数中释放资源。 C++ class type 对象的生命期可以由程序完全控制,而 C# 引用类型对象是被托管的,万一处理到关键任务时 GC 造成卡顿一下,也是蛮郁闷的。这篇博客主要总结一下 C++ 析构函数,以下内容参考《 C++ Primer 4th 》 13.3 The Destructor 小节。

■ C++ 的析构函数在哪些情况下会执行呢?

  • The destructor is run only when a pointer to a dynamically allocated object is deleted or when an actual object (not a reference to the object) goes out of scope.
  • The destructor is not run when a reference or a pointer to an object goes out of scope.
  • Destructors are also run on the elements of class type in a container whether a library container or built-in array when the container is destroyed.

前两点没有什么好说的,对第三点提一下。当 STL 容器或者内部数组中存放的是 class type 对象元素,那么当容器或者数组销毁时,各元素的析构函数也会被执行(如果自己编写容器类,不要忘记这个功能点)。如下代码。

{
    Foo *p = new Foo[10];
    vector<Foo> vec(p, p + 10);
    delete p; // array is freed, destructor run on each element
    // vec goes out of scope, call destructor on each element
}

但是后面紧接着说,容器中的元素逆序被析构,也就说 size() - 1 这个元素最先被析构。我翻了一下 msys2 平台 g++ 6.2.0 版本中的 vector 源码,发现它的析构函数最终会调用下面的函数(文件 stl_construct.h ),反而是从 0 索引开始析构的。所以书上这句话看看就行了,别当真。我测试了一下,数组中的元素倒是按逆序被析构的。

template<bool>
struct _Destroy_aux
{
    template<typename _ForwardIterator>
    static void
    __destroy(_ForwardIterator __first, _ForwardIterator __last)
    {
      for (; __first != __last; ++__first)
        std::_Destroy(std::__addressof(*__first));
    }
};

■ 什么时候需要编写类的析构函数呢?

通常情况下并不需要为类编写析构函数,如果需要在对象析构时处理一些事情,比如释放资源,那么就需要编写析构函数。书中提到 Rule of Three 就是指,如果一个类需要析构函数,那么就还需要 copy control 其他成员(拷贝构造函数,赋值操作符, C++11 的移动构造函数)。
然后书中提到编译器总会合成一个析构函数。关于这个合成的析构函数有如下要点。

  • Unlike the copy constructor or assignment operator, the compiler always synthesizes a destructor for us. The synthesized destructor destroys each nonstatic member in the reverse order from that in which the object was created.
  • For each member that is of class type, the synthesized destructor invokes that member's destructor to destroy the object.
  • Destroying a member of built-in or compound type has no effect. In particular, the synthesized destructor does not delete the object pointed to by a pointer member.
  • An important difference between the destructor and the copy constructor or assignment operator is that even if we write our own destructor, the synthesized destructor is still run.

第二点是编译器合成的析构函数会自动执行 class type 成员的析构函数。第三点中的 built-in type 指 int float 等这种类型, compound type 指指针,引用这种类型,编译器合成的析构函数对这两种类型没有影响。由于编译器合成的析构函数负责成员变量的析构工作,对第四点就会觉得很理所当然,那就是虽然类中定义了析构函数,但是编译器合成的析构函数还是会执行。


■ 一个简单的内存池

经过上面的总结,我们知道 C++ 构造函数和析构函数完全与对象的生命周期同步。那么开发 C++ 内存池时,如何在已经分配好的内存空间上构造对象和析构对象呢。
一个内存池的基本逻辑有:内存池的空间管理、对象的构造、对象的回收、标识已分配对象的唯一 handle 。
下面举了一个简单的 MemObject ,只能构造一个对象,这样逻辑会很简单,让我们专注于对象的构造和回收。在 Alloc 时会在已经分配的内存上调用对象的习惯函数,在 Free 时会调用对象的析构函数,再设置 use_ 为 false ,使得这块内存再次被使用。

#include <new>
#include <stdio.h>
#include <stdlib.h>

template<typename T>
class MemObject {
public:
	MemObject() 
		: use_(false), handle_(0) {
		pObj_ = static_cast<T*>(malloc(sizeof(T)));
	}

	~MemObject() {
		free(pObj_);
	}

	T* Alloc(unsigned int &hdl) {
		if (use_) {
			printf("object is using\n");
			return NULL;
		}
		hdl = ++handle_;
		use_ = true;
		new(pObj_) T();
		return pObj_;
	}

	void Free(unsigned int hdl) {
		if (!use_ || hdl != handle_) {
			printf("invalid free, use:%d hdl:%d\n", use_, handle_);
			return;
		}
		use_ = false;
		pObj_->~T();
	}

	T* Get(int hdl) {
		if (!use_ || hdl != handle_) {
			printf("invalid get, use:%d hdl:%d\n", use_, handle_);
			return NULL;
		}
		return pObj_;
	}

private:
	MemObject(const MemObject&) {}
	MemObject& operator=(const MemObject&) {}

	T *pObj_;
	bool use_;
	unsigned int handle_;
};

struct Foo {
	Foo() {
		printf("Foo ctor\n");
	}

	~Foo() {
		printf("Foo ~ctor\n");
	}

	void Test() {
		printf("Test in Foo\n");
	}
};

int 
main() {
	MemObject<Foo> mo;
	Foo *ptr;
	unsigned int hdl1, hdl2;

	ptr = mo.Alloc(hdl1);
	ptr->Test();
	mo.Free(hdl1);

	printf("\n");
	mo.Free(hdl1);
	ptr = mo.Alloc(hdl2);
	ptr->Test();
	mo.Alloc(hdl1);
	mo.Free(hdl2);
	return 0;
}

编译 g++ -o t test.cpp 后,运行结果如下。

$ ./t.exe
Foo ctor
Test in Foo
Foo ~ctor

invalid free, use:0 hdl:1
Foo ctor
Test in Foo
object is using
Foo ~ctor

© 著作权归作者所有

YLMe
粉丝 16
博文 47
码字总数 51106
作品 0
广州
程序员
私信 提问
C++基础教程之构造函数与析构函数

构造函数 当我们需要在对象创建时初始化一些数据的时候,我们不可能提供一个普通的成员方法供程序猿在对象创建后调用。因为如果程序猿故意或者无意间忘记了调用该方法,就可能导致程序出现偏...

这个人很懒什么都没留下
2018/09/08
0
0
malloc和new有什么区别

malloc和new有以下不同: new、delete是操作符,可以重载,只能在c++中使用。 malloc、free是函数,可以覆盖,c、c++中都可以使用。 new可以调用对象的构造函数,对应的delete调用相应的析构...

夏雪冬日
2012/12/11
0
0
消灭“脑细胞杀手”,阿里专家带你深入C++对象的生命周期管理

摘要:C/C++的指针一直是令人又爱又恨的特性。围绕指针产生了许许多多优雅的数据结构和系统实现,但又滋生了不少“脑细胞杀手”——内存Bug。C/C++指针问题(空指针、野指针、垂悬指针)的根...

萌萌怪兽
2018/04/18
0
0
转帖关于new/delete的运算符和malloc()/free()的标准库函数

new--------delete malloc--------free 问题: 我又一个对象类,里面有一个指针链表,动态分配空间,在析构的时候释放。开始对对象进行new操作,但是执行delete对象操作的时候出错,提示在析...

lixun
2012/08/29
243
0
[C++再学习系列] 全局或静态变量(对象)的初始化

  对于C语言的全局和静态变量,不管是否被初始化,其内存空间都是全局的;如果初始化,那么初始化发生在任何代码执行之前,属于编译期初始化。由于内置变量无须资源释放操作,仅需要回收内...

技术小美
2017/11/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

基于Prometheus和Grafana的监控平台 - 环境搭建

相关概念 微服务中的监控分根据作用领域分为三大类,Logging,Tracing,Metrics。 Logging - 用于记录离散的事件。例如,应用程序的调试信息或错误信息。它是我们诊断问题的依据。比如我们说...

JAVA日知录
52分钟前
5
0
PHP运行时全局构造体

struct _php_core_globals { zend_bool magic_quotes_gpc; // 是否对输入的GET/POST/Cookie数据使用自动字符串转义。 zend_bool magic_quotes_runtime; //是否对运行时从外部资源产生的数据使...

冻结not
54分钟前
4
0
webpack插件html-webpack-plugin

本文转载于:专业的前端网站→webpack插件html-webpack-plugin 1、插件安装 npm install html-webpack-plugin --save-dev 2、插件使用 webpack.config.js配置文件为: var htmlWebpackPlugin=...

前端老手
今天
6
0
数据挖掘

zhengchen1996
今天
4
0
nginx配置反向代理

文章来源 运维公会:nginx配置反向代理 1、简介 Nginx最为常见的一种功能就是配置反向代理。配置也是十分的简单,只需要用到proxy模块即可。 怎么查看nginx默认的安装模块? 在nginx的安装目...

运维团
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部