STL空间配置策略之第一级配置器__malloc_alloc_template

SGI STL中并没有使用传统的allocator作为空间配置工具,虽然allocator符合STL对于空间配置器的基本要求,但是allocator实质上只是对C++内置的::operator new和::operator delete做了一层包装:

	template<class T>
	inline T * allocate(ptrdiff_t size, T *)
	{
		set_new_handler(0);
		T * tmp = (T *)(::operator new((size_t)(size * sizeof(T))));
		if (tmp == 0)
			cerr << "out of memmory" << endl;
		return temp;
	}

如上的allocator::allocate只是简单调用了new,这样其实效率很低,尤其是对于很多复杂的状况。如频繁请求小块内存有可能会导致内存内部碎片的产生(由于内存对齐等原因)。或者有一些内置类型无需构造或析构操作(type_traits致力于解决此问题)。

SGI STL为了解决这些问题,提供了一个特殊的空间配置器,其思路是首先将配置内存与对象构造分离。SGI STL将空间分配器分装在两个头文件中,一个是<stl_construct.h>,其中操作主要负责对象构造;另一个是<stl_alloc.h>,其中操作主要负责配置内存。

其次是将大块内存的分配与小块内存的分配分开讨论。在这一点上SGI STL选择使用两级处理装置:第一层负责处理大块内存的请求,第二层主要处理小块内存的请求。

那么我们在使用时如何指定使用哪一个内存分配装置呢?

在这一问题上,SGI STL使用了预处理命令:

#ifdef _USE_MALLOC
typedef _malloc_alloc_template<0> malloc_alloc;
typedef malloc_alloc alloc;//第一级配置器
#else
typedef _default_alloc_template<__NODE_ALLOCATOR_THREADS,0> alloc //第二级配置器
#endif
通过预处理命令在第一级或第二级配置器之间选择一个命名为alloc,再为类模板设置第二个模板类型参数:
template<typename T,class Alloc=alloc>
class vector
{
	...
};

因为我们将_malloc_alloc_template或_default_alloc_template设置为底层的空间配置器,所以我们还需要设计一个包装函数使其符合常规的空间配置器的使用方式,还可以对底层实现进行更好的封装:

/*
simple_alloc为底层的内存分配类的外部包装,其成员全部调用_malloc_alloc_template
的成员
*/
template<typename T,class Alloc=alloc>
class simple_alloc
{
public:
	static T * allocate(void)
	{
		return (T *)Alloc::allocate(sizeof(T));
	}
	static T * allocate(size_t n)
	{//此allocate接受一个指定对象个数的参数n
		return n == 0 ? nullptr : (T *)Alloc::allocate(sizeof(T)*n);
	}
	static void deallocate(T * p)
	{
		Alloc::deallocate(p, sizeof(T));
	}
	static void deallocate(T * p, size_t n)
	{
		if (n != 0)
			Alloc::deallocate(p);
	}
};

这样我们便可以这样使用它们:

template<typename T,class Alloc=alloc>
class vector
{
	typedef T value_type;
protected:
	typedef simple_alloc<value_type, Alloc> data_allocator;
	...
		void deallocate()
	{
		if (...)
			data_allocator::deallocate(start, end_of_storge - start);
	}
	...
};


第一级配置器的实质是使用了malloc、realloc、free。并处理了内存不足(请求内存失败)的情况,因为::operator new提供了set_new_handler操作,使用户可以自己定制发生内存不足时的处理策略。SGI STL并不能使用C++的set_new_handler,所以单独实现了这个功能。

下面是第一级配置器的实现:

template<int inst>
class _malloc_alloc_template
{
	/* oom_alloc为静态函数成员,用于处理malloc时的内存不足问题
	   oom_realloc为静态函数成员,用于处理realloc时的内存不足问题
	   _malloc_alloc_handler为静态数据成员,为void(*)()类型的函数指针,用于用户自
	   己制定内存分配策略
	*/
	static void * oom_malloc(size_t);//out_of_memmory malloc
	static void * oom_realloc(void *, size_t);
	static void(*_malloc_alloc_oom_handler)();
public:
	static void * allocate(size_t n)
	{
		void * result = malloc(n);//请求内存
		if (result == nullptr)//如果内存不足
			result=oom_malloc(n);//调用oom_malloc
		return result;
	}
	static void * reallocate(void * p, size_t n)
	{
		void *result = realloc(n);
		if (result == nullptr)
			result = oom_realloc(p, n);
		return result;
	}
	static void deallocate(void * p)
	{
		//使用free函数释放p地址后所分配的内存块
		free(p);
	}

	/*此静态成员函数接受一个void(*)()类型的函数指针作为参数,返回
	void(*)()类型的函数指针。其作用为用用户自己定制的内存调度方法替换
	_malloc_alloc_handler,由此实现类似C++的set_new_handler方法。
	*/

	static void(* set_malloc_handler(void(*f)()))()
	{
		void(*old)() = _malloc_alloc_oom_handler;
		_malloc_alloc_oom_handler = f;
		return old;
	}
};

template<int inst>
void(*_malloc_alloc_template<inst>::_malloc_alloc_oom_handler)() = 0;

template<int inst>
void * _malloc_alloc_template<inst>::oom_malloc(size_t n)
{
	void(*my_oom_handler)();
	void * result;
	//无限循环,直至成功分配内存或用户没有定制内存分配策略
	for (;;)
	{
		my_oom_handler = _malloc_alloc_oom_handler;
		if (my_oom_handler == nullptr)//如果用户没有定制内存分配策略
			exit(1);
		(*my_oom_handler)();//使用用户定制的方法
		result = malloc(n);
		if (result)
			return result;
	}
}
template<int inst>
void * _malloc_alloc_template<inst>::oom_realloc(void * p, size_t n)
{
	//此函数的设计思路与oom_malloc如出一辙
	void(*my_oom_handler)();
	void * result;
	for (;;)
	{
		my_oom_handler = _malloc_alloc_oom_handler;
		if (my_oom_handler == nullptr)
			exit(1);
		(*my_oom_handler)();
		result = realloc(p,n);
		if (result)
			return result;
	}
}


注意,SGI STL的第一级配置器的allocate()与reallocate()都是在malloc()和realloc()调用失败后跳转到oom_malloc与oom_realloc上的,后两者内有循环,不断条用“内存不足处理例程”。但如果用户没有定制处理策略,则会直接exit()生硬的停止程序。

记住,设计"内存不足处理例程"是客户端的责任,设定“内存不足处理例程”也是客户端的责任。


/*本文部分内容引用《STL源码剖析》侯捷著*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值