Winx基本类使用指南之C++标准扩展(AutoFreeAlloc)[注1]
在C++中,一般有两种内存分配的方式:
1. 传统的配对方式
Ø C++中的new与delete
Ø C中alloc与free
Ø Windows API中GlobalAlloc与GlobalFree、HeapAlloc与HeapFree等
这种分配方式首先一件事就是要记住在分配内存后一定要释放,这一点在吃过几次亏后总是能够记住;但是在某个程序段的分支或异常处理多起来时(并且这个程序段不止一次的分配内存),再使用这种内存分配方式就会头大如斗(我想,绝大多数的C++程序员都应该对此有所体会)。
2. 使用智能指针
使用智能指针可以解决传统的配对方式的问题,但同时却又引出了新的问题。智能指针品种很多,记住它们的各种规则更是困难。本人愚苯,每次使用boost库中的智能指针,总是先要去看它们的帮助文档。
现在终于有了第三种内存分配方式,这就是Winx中AutoFreeAlloc机能(这个机能属于Winx的标准C++的扩展部分),其基本原理是由一个AutoFreeAlloc负责某程序段中所有的内存分配,而不管所分配内存是由什么对象使用,在该程序段结束时,再由这个AutoFreeAlloc释放所有被分配的内存。对于具体的底层实现机制,感兴趣的朋友可以参照xushiwei的最袖珍的垃圾回收器。
下面就转入正题,介绍一下AutoFreeAlloc的使用方法及在使用中要注意的一些问题。
使用AutoFreeAlloc的例子
下面的例子主要演示了使用AutoFreeAlloc的一些方式(在main函数中),其主要功能是首先定义一个分配器,然后为一系列对象分配内存。
class ClassA
{
private:
int var_int_;
double var_double_;
public:
ClassA() { }
};
class ClassB
{
private:
int var_int_;
double var_double_;
public:
ClassB() : var_int_(0), var_double_(0.0) { }
ClassB(const ClassB& cb) :
var_int_(cb.var_int_), var_double_(cb.var_double_)
{}
~ClassB() { }
};
class ClassC
{
private:
int var_int_;
double var_double_;
ClassB class_b_;
public:
ClassC() { }
~ClassC() { }
};
int main()
{
AutoFreeAlloc alloc;
ClassA* ca = STD_NEW(alloc, ClassA);
ClassA* caArray = STD_NEW_ARRAY(alloc, ClassA, 100);
for (int i = 0; i < 100; ++i)
{
// print address of each element of Array caArray
cout << caArray + i << endl;
}
ClassB cbTemp;
// assign cbTemp to cb
ClassB* cb = STD_NEW(alloc, ClassB)(cbTemp);
ClassB* cbArray = STD_NEW_ARRAY(alloc, ClassB, 100);
ClassC* cc = STD_NEW(alloc, ClassC);
ClassC* ccArray = STD_NEW_ARRAY(alloc, ClassC, 100);
// assign 1 to i1
int* i1 = STD_NEW(alloc, int)(1);
int* i2 = STD_NEW_ARRAY(alloc, int, 100);
// assign 0.0 to d1
double* d1 = STD_NEW(alloc, double)(0.0);
double* d2 = STD_NEW_ARRAY(alloc, double, 100);
int* i3 = STD_ALLOC(alloc, int);
int* i4 = STD_ALLOC_ARRAY(alloc, int, 100);
double* d3 = STD_ALLOC(alloc, double);
double* d4 = STD_ALLOC_ARRAY(alloc, double, 100);
char* s1 = STD_NEW(alloc, char);
*s1 = 'T';
cout << *s1 << endl;
char* s2 = STD_NEW_ARRAY(alloc, char, 128);
strcpy(s2, "hello world!");
cout << s2 << endl;
char* s3 = STD_NEW(alloc, char);
char* s4 = STD_NEW_ARRAY(alloc, char, 128);
strcpy(s4, "hello world 2!");
cout << s4 << endl;
return 0;
}
通过上面的例子可以看出,AutoFreeAlloc的使用非常简单,只有2个步骤:
1. 定义AutoFreeAlloc
2. 通过STD_NEW、STD_NEW_ARRAY、STD_ALLOC、STD_ALLOC_ARRAY这4个宏中的一个来分配内存(或者说创建对象),这是不是有点象java中的new操作呢?J
内存分配器的定义
内存分配器的定义是用main函数中的第一条语句来完成的:
AutoFreeAlloc alloc;
这条语句很简单,但一些幕后的细节我们需要了解。我们再来看AutoFreeAlloc是有一个typedef来定义的:
typedef AutoFreeAllocT<StdLibAlloc> AutoFreeAlloc;
而StdLibAlloc的定义如下:
struct StdLibAlloc
{
static void* allocate(size_t cb)
{ return malloc(cb); }
static void* allocate(size_t cb, DestructorType fn)
{ return malloc(cb); }
static void* allocate(size_t cb, int fnZero)
{ return malloc(cb); }
static void deallocate(void* p) { free(p); }
static void deallocate(void* p, size_t) { free(p); }
static void swap(StdLibAlloc& o) {}
};
从这里可以看出,我们使用AutoFreeAllocT来管理分配的内存,StdLibAlloc进行实际的内存分配(只是简单的使用alloc、free函数对)。这样就提供了一种机制,我们可以自己定义实际的内存分配方式。
现实世界中,充满了各种各样的内存分配方式。比如说我们需要创建映射文件(CreateFileMapping)来作为内存使用,或者说我们要使用共享内存,都可以建立自己的分配器,然后统一用AutoFreeAllocT进行管理(只要建立的分配器符合要求—类似于StdLibAlloc实现6个函数)。
一般情况下,只要直接使用AutoFreeAlloc就可以了。但为了适应特别需要,这里简单介绍了一下StdLibAlloc。
为对象分配内存
为对象分配内存时非常简单,只要使用以下任意一个宏即可:
STD_NEW(alloc, Type)
STD_ALLOC(alloc, Type)
其中的alloc指定分配器,Type指定对象类型。STD_NEW与STD_ALLOC之间的区别下文说明。
为数组分配内存
为数组分配内存时,可以使用以下宏:
STD_NEW_ARRAY(alloc, Type, count)
STD_ALLOC_ARRAY(alloc, Type, count)
其中的alloc指定分配器,Type指定对象类型,count指定数组的大小。STD_NEW_ARRAY与STD_ALLOC_ARRAY之间的区别下文说明。
STD_NEW(STD_NEW_ARRAY)与STD_ALLOC(STD_ALLOC_ARRAY)的区别
STD_NEW宏先分配内存,然后使用placement new操作符来构造对象,释放内存时先调用被构造对象的析构函数,然后再释放内存(针对C++中的原生类型(int,char,long,double等),会使用模板特化技术,消除对原生类型的析构调用)。
STD_ALLOC宏只是分配内存,释放内存时直接释放。
STD_NEW_ARRAY与STD_ALLOC_ARRAY之间的区别与此类似。
释放所分配的内存
AutoFreeAlloc会自动释放内存,你也可以通过调用AutoFreeAllo的成员函数clear()手工释放内存(通常不需要)。
一点补充:释放内存时如何调用对象的析构函数
虽然是否知道AutoFreeAlloc释放内存时如何调用对象的析构函数对我们使用AutoFreeAlloc没有影响,但理解这一点,在我们使用AutoFreeAlloc时更能够做到心中有数。
我们知道(参照xushiwei的最袖珍的垃圾回收器),在AutoFreeAlloc的allocate函数中指定了对象的清除函数(这里使用清除是为了与析构区别开来),请见如下代码:
template <class _Alloc, int _MemBlockSize = MEMORY_BLOCK_SIZE>
class AutoFreeAllocT
{
void* winx_call allocate(size_t cb, DestructorType fn);
};
其中的DestructorType参数指定了对象的清除函数。其定义如下:
typedef void __FnDestructor(void* data);
typedef __FnDestructor* DestructorType;
我们再来看STD_NEW的宏定义:
#define STD_NEW(alloc, Type) /
::new((alloc).allocate(sizeof(Type), /
std::DestructorTraits<Type>::destruct)) Type
DestructorTraits的定义如下:
template <class Type>
struct DestructorTraits
{
static void winx_call destruct(void* data)
{
((Type*)data)->~Type();
}
};
通过这些代码片段,我们就可以知道AutoFreeAlloc释放内存时会调用DestructorTraits<Type>::destruct函数,而在DestructorTraits<Type>::destruct函数中会调用对象的析构函数。
对于数组,其做法与此类似,这里就不详细讲了。