C++资源管理

对Effective C++在资源管理部分的内容进行总结。

C++运行时内存

C++运行时内存主要分为常量区、全局/静态变量区、栈区、堆区和自由存储区。其中前两者存在于程序的整个生命周期,栈中的变量由编译器自动分配和清除,所以称作自动变量。堆区由new调用构造函数初始化,由delete调用析构函数回收。自由存储区由malloc分配一块指定大小的内存,由free释放。

RAII

可以看出对于动态分配的内存(堆和自由存储)必须能够在适当时机调用delete/free进行释放,否则会造成泄露,也有可能某处代码在先前已经delete/free了,造成悬空指针undefined behaviour。当然可以建立一张表(称为对象池)登记这些指针,当满足一些条件的时候进行删除的手动管理。不过最好的方法是通过RAII借助编译器管理指针。
编译器的自动回收机制包含了栈和对象两种。其中栈能够管理自动对象,但是它只会清除指针本身而非指针指向对象。

复制

一般对象之间的复制行为分为4种:

  1. 浅复制:浅复制也是默认复制构造函数的实现,将源对象中的成员复制到新的对象中。因此如果源对象中存在指针,那么实际上源对象和新对象是共享指针指向的对象的,这并不是一个错误的逻辑,但是问题在于新老对象都没有意识到自己和别的对象共享着资源,如果存在析构函数(除非使用手动管理,否则必然要有析构函数用来释放指针指向对象),那么必然会造成悬空指针。
  2. 深复制:需要自定义复制构造函数,在复制行为发生时递归地建立对象成员以及指针指向对象的副本。
  3. 资源控制权转移:资源占用具有排他性
  4. 资源控制权共享:类似于浅复制,但是这种方案解决了资源释放的问题,对于需要共享的资源设置引用计数,当引用计数变为0时销毁对象,而不再通过构造函数。

深复制

在深复制中可能存在一个问题,假设有若干个派生类继承基类Derived_i : public Base,现在有一个Base * d,但是不知道具体类型,现在希望对这个基类进行深复制。直接调用基类的复制构造函数Base p = new Base(d)显然是行不通的,一方面C++没有提供虚复制构造函数。常用的办法是自己定义一个clone函数。同样地,对于其他的构造函数,也是不存在多态的,因此如果需要对于不同的参数返回不同的派生类的指针,可以通过定义一个工厂函数来解决。
对于没有使用继承或派生的类型,当然可以定义三个函数(复制、赋值、析构)进行深复制,假设复制对象obj,可以认为复制了一棵树,树中任何节点(成员)的析构都会导致该节点为树根的子树被删除,所以对于这棵树只能修改树根或叶子,否则会造成内存泄露。

复制构造函数和赋值构造函数

复制构造函数指的形如T(const T &)的构造函数,涉及到C++的复制初始化。其中复制构造函数常被定义为explicit的,此时必须显式地使用该构造函数。在C++11标准之后扩大了范围,将非explicit构造函数都称为转换构造函数。这里要区分转换构造函数T::T(const U &)和类型转换运算符operator T::U(),前者是从U构造T,后者是从自己T构造U。类型转换运算符有很多的作用,常见的是实现将函数返回值加入重载决议。
赋值运算符指的形如T & operator=(const T &)的运算符,指的是赋值而不是初始化操作。