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