空间配置器

“new”的实现

这里直接我们直接来看STL的construct实现吧

1
2
3
4
5
6
// 这里的construct调用的是placement new, 在一个已经获得的内存里建立一个对象
template <class T1, class T2>
inline void construct(T1* p, const T2& value)
{
 new (p) T1(value);
}

可以明白这里就只是一个placement new的调用, 只是用了泛型来实现一个对象分配的模板, 并实现初始化.

既然已经看到了对象的分配, 那再接再厉看看空间的分配, 充分了解STL是怎么将new分开执行的. allocate函数实现空间的申请, 但是这里有一点看不出来, 申请内存是有分为一级配置器和二级配置器, 分配的空间小于128字节的就调用二级配置器, 大于就直接使用一级配置器, 一级配置器直接调用malloc申请, 二级使用内存池.

1
2
3
4
5
6
7
8
9
template <class T> inline T *allocate(ptrdiff_t size, T *) {
std::set_new_handler(0);
T *tmp = (T *)(::operator new((size_t)(size * sizeof(T))));
if (!tmp) {
std::cerr << "out of memort" << std::endl;
exit(1);
}
return tmp;
}

new_handler,顾名思义就是一个处理程序,当程序向内存的分配请求无法满足时将有两种可能:

  1. 抛出异常
  2. 设置一个异常处理函数,这就是所谓的new_handler(类似于中断机制,本质上来说就是一个函数指针)

当第二种情况发生以后,我们可以通过new_handler删除无用的内存,以及设置新的new_handler,而这个set_new_handler就是来进行设置的。

set_new_handler(0)主要是为了卸载目前的内存分配异常处理函数,这样一来一旦分配内存失败的话,C++就会强制性抛出std:bad_alloc异常,而不是跑到处理某个异常处理函数去处理。

::访问符放到最前面的意思是使用全局版本

new一共有三兄弟:

  1. new operator (就是我们常用的new)
  2. operator new
  3. placement new

先说说这个new,我们在程序中使用new的时候,实际上做了两件事情:申请内存和构造对象
简单的理解,new完成了一套比较完备的服务,而operator new,只是申请内存,placement new是在申请的内存中进行构造对象,第2、3中形式就是对new的拆分。

1
2
3
4
5
template<class T1, class T2>
inline void _construct(T1* p, const T2& value)
{
new(p)T1(value);//placement new,invoke constuctor of t1
}

这里p指向一块内存,我们直接在这个内存上构造对象(调用T1的构造函数),即

1
p->T1::T1(value)

内存分配果然是调用operator new来执行空间分配, 这里allocateconstruct都只是简单的对operator new进行封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const int N = 4;
int main() {
std::allocator<std::string> alloc;
auto str_ve = alloc.allocate(N);
auto p = str_ve; // vector<string> *p = str_ve;
alloc.construct(p++);
alloc.construct(p++, 10, 'a');
alloc.construct(p++, "construct");
std::cout << str_ve[0] << std::endl;
std::cout << str_ve[1] << std::endl;
std::cout << str_ve[2] << std::endl;

while (p != str_ve) {
alloc.destroy(--p);
}
alloc.deallocate(str_ve, N);

exit(0);
}

运行结果:

1
2
3

aaaaaaaaaa
construct

这个程序首先调用allocate申请N个大小的空间, 在依次construct调用构造函数, 这里就先初始化3个结构, 紧接着通过destory调用析构函数, 最后deallocate释放申请的空间. 整个过程很容易理解, 但是这里还要深入是dealllocate和destroy两个函数.

“delete”实现

先是看destroy调用析构函数. 而destroy有两个版本.

版本一:

需要传入的参数 : 一个指针

1
2
3
4
5
// 第一版本, 接收指针
template <class T> inline void destroy(T* pointer)
{
   pointer->~T();
}

版本一直接就调用了析构函数, 不用过多的分析.

版本二:

需要传入的参数 : 两个迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 第二个版本的, 接受两个迭代器, 并设法找出元素的类型. 通过__type_trais<> 找出最佳措施
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last)
{
 __destroy(first, last, value_type(first));
}

// 接受两个迭代器, 以__type_trais<> 判断是否有traival destructor
template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*)
{
 typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
 __destroy_aux(first, last, trivial_destructor());
}

destroy直接调用__destroy, 前者只是一个接口, 所以重点是在后者.

分析__type_traits<> : 它是用于获取迭代器所指对象的类型,运用traits技法实现的.只要记住我们用来获取对对象类型就可以了. 然后通过类型的不一样选择执行不同的析构调用.

第二版本接受first和last两个迭代器,将两个迭代器范围内的对象析构掉。在第二版本中运用了traits编程技法,traits会得到当前对象的一些特性,再根据特性的不同分别对不同特性的对象调用相应的方法。在第二版本中,STL会分析迭代器所指对象的has_trivial_destructor特性的类型(只有两种:_true_type__false_type),如果是__true_type,STL就什么第一不做;如果是false_type,就会调用每个对象的析构函数来销毁这组对象。

__type_traits__false_type时, 调用的是下面这个函数, 通过迭代所有的对象并调用版本一的函数执行析构函数进行析构. 而这个是被称为non-travial destructor

1
2
3
4
5
6
7
// 没有non-travial destructor 
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type)
{
for ( ; first < last; ++first)
destroy(&*first);
}

__type_traits__true_type时, 什么也不做, 因为这样效率很高效, 并不需要执行析构函数. 而这个是被称为travial destructor.

1
2
3
// 有travial destructor
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}

最后是版本二的特化版, 同样也什么都不用做, 没有必要做析构.

1
2
inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}

destroy分为这么几个版本和几个不同的函数执行都是为了提升效率, 较小的调用并不能看出什么, 但是如果是范围析构的话这样不同的选择析构能很节约时间和效率.

完整实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#ifndef _JJALLOC_H_
#define _JJALLOC_H_

#include <climits>
#include <iostream>

namespace JJ {
// 使用operator new分配空间
template <class T> inline T *_allocte(ptrdiff_t size, T *) {
std::set_new_handler(0);
T *tmp = (T *)(::operator new((size_t)(size * sizeof(T))));
if (tmp == 0) {
std::cerr << "out of memory" << std::endl;
exit(1);
}
return tmp;
}

// 使用operator delete回收空间
template <class T> inline void _deallocate(T *buffer) {
::operator delete(buffer);
}

// 在指定内存上构造一个对象
template <class T1, class T2> inline void _construct(T1 *p, const T2 &value) {
// placement new
new (p) T1(value);
}

// 析构一个对象
template <class T> inline void _destroy(T *ptr) { ptr->~T(); }

// 遵循allocator的标准定义相关结构
template <class T> class allocator {
public:
typedef T value_type;
typedef T *pointer;
typedef const T *const_pointer;
typedef T &reference;
typedef const T &const_reference;
typedef size_t sizt_type;
typedef ptrdiff_t difference_type;

template <class U> struct rebind { typedef allocator<U> other; };

pointer allocate(sizt_type n, const void *hint = 0) {
return _allocte((difference_type)n, (pointer)0);
}

void deallocate(pointer p, sizt_type n) { _deallocate(p); }

void construct(pointer p, sizt_type n) { _destroy(p); }

void destroy(pointer p) { _destroy(p); }

const_pointer const_address(const_reference x) { return (const_pointer)&x; }

sizt_type max_size() const { return sizt_type(UINT_MAX / sizeof(T)); }
};

} // namespace JJ
#endif /* ifndef _JJALLOC_H_ */