这是stl源码阅读系列的第三篇,这一篇来看看stl中对象的构造与析构是如何处理的。
1. placement new·
通常来说new操作分为两步:
- 分配内存buffer
- 在buffer上构造对象(调用构造函数)
其中第一步称为operator new
, 第二步称为placement new
。两者的使用例子如下:
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
| #include <iostream>
class MyClass { public: MyClass() { std::cout << "MyClass constructor called!" << std::endl; }
~MyClass() { std::cout << "MyClass destructor called!" << std::endl; } };
int main() { MyClass *p1 = new MyClass(); delete p1;
void *memory = ::operator new(sizeof(MyClass)); MyClass *p2 = new (memory) MyClass(); p2->~MyClass(); ::operator delete(memory); return 0; }
|
了解这种语法后,回到主题,系列第一篇已经介绍了stl默认分配器的工作原理,既然已经有了需要的内存,stl是如何在这些内存上进行构造和析构的呢?
2. stl_construct.h·
关于stl的构造和析构,源码文件在 allocator/stl_construct.h
中。
1. construct·
先看 construct
接口:
1 2 3 4 5 6 7 8 9 10
| template <class _T1, class _T2> inline void construct(_T1* __p, const _T2& __value) { _Construct(__p, __value); }
template <class _T1> inline void construct(_T1* __p) { _Construct(__p); }
|
两个函数都接受 __p
参数,即要在__p
所指内存上构造对象。第一个construct
还允许赋一个初值。
如果是多参数构造器,怎么实现?是因为老版本stl所采用的c++版本还没有变参模板么?
_Construct
函数如下:
1 2 3 4 5 6 7 8 9 10
| template <class _T1, class _T2> inline void _Construct(_T1* __p, const _T2& __value) { new ((void*) __p) _T1(__value); }
template <class _T1> inline void _Construct(_T1* __p) { new ((void*) __p) _T1(); }
|
这里就用到了 placement new
技术,很简单,不再赘述。
2. destroy·
再来看看destroy:
1 2 3 4 5 6 7 8 9 10
| template <class _Tp> inline void destroy(_Tp* __pointer) { _Destroy(__pointer); }
template <class _ForwardIterator> inline void destroy(_ForwardIterator __first, _ForwardIterator __last) { _Destroy(__first, __last); }
|
第一个版本_Destroy
:
1 2 3 4 5
| template <class _Tp> inline void _Destroy(_Tp* __pointer) { __pointer->~_Tp(); }
|
直接调用了_Tp
类型的析构函数。
如果是builtin的类型,无对应的析构函数,也未发现特例化版本,是否不应该直接调用第一个版本的destroy
TODO: 需要在后文分析容器时,查看destroy
的具体用法
第二版本 _Destroy
:
1 2 3 4 5
| template <class _ForwardIterator> inline void _Destroy(_ForwardIterator __first, _ForwardIterator __last) { __destroy(__first, __last, __VALUE_TYPE(__first)); }
|
这里的 __VALUE_TYPE
宏是关键,看下它的定义:
1 2 3 4 5 6 7 8 9
| #define __VALUE_TYPE(__i) __value_type(__i)
template <class _Iter> inline typename iterator_traits<_Iter>::value_type* __value_type(const _Iter&) { return static_cast<typename iterator_traits<_Iter>::value_type*>(0); }
|
这就用到本系列二介绍的技术–traits技术。从结果来看, __VALUE_TYPE
返回的是迭代器所指向类型的一个指针。
再看 __destroy
是如何实现的:
1 2 3 4 5 6 7 8 9
| template <class _ForwardIterator, class _Tp> inline void __destroy(_ForwardIterator __first, _ForwardIterator __last, _Tp*) { typedef typename __type_traits<_Tp>::has_trivial_destructor _Trivial_destructor; __destroy_aux(__first, __last, _Trivial_destructor()); }
|
这里又用到了内置类型声明技术,实际上是去看 _Tp
的 has_trivial_destructor
是什么类型, 看下 __type_traits
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| template <class _Tp> struct __type_traits { typedef __true_type this_dummy_member_must_be_first; typedef __false_type has_trivial_default_constructor; typedef __false_type has_trivial_copy_constructor; typedef __false_type has_trivial_assignment_operator; typedef __false_type has_trivial_destructor; typedef __false_type is_POD_type; };
struct __true_type { };
struct __false_type { };
|
可以看到默认 __type_traits
模板中的 has_trivial_destructor
为 __false_type
。 __true_type
和__false_type
为两个空成员的结构体类型,用于表示_Tp
是否是has_trivial_destructor
, 如果是 __true_type
则不用调用_Tp
对象的析构函数(比如内置类型),否则则调用析构函数。
让我们返回到__destroy
函数的__destroy_aux
:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
template <class _ForwardIterator> void __destroy_aux(_ForwardIterator __first, _ForwardIterator __last, __false_type) { for ( ; __first != __last; ++__first) destroy(&*__first); }
template <class _ForwardIterator> inline void __destroy_aux(_ForwardIterator, _ForwardIterator, __true_type) {}
|
可以看到,实现符合上文所说,__false_type
循环调用迭代器所指对象的析构函数(前文说了destroy内部是调析构)。而__true_type
是空实现。
默认的 __type_traits
has_trivial_destructor为__false_type
,哪些情况为 __true_type
?
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
| #ifndef __STL_NO_BOOL
__STL_TEMPLATE_NULL struct __type_traits<bool> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
#endif
__STL_TEMPLATE_NULL struct __type_traits<char> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
__STL_TEMPLATE_NULL struct __type_traits<signed char> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
__STL_TEMPLATE_NULL struct __type_traits<unsigned char> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
#ifdef __STL_HAS_WCHAR_T
__STL_TEMPLATE_NULL struct __type_traits<wchar_t> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
#endif
__STL_TEMPLATE_NULL struct __type_traits<short> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
__STL_TEMPLATE_NULL struct __type_traits<unsigned short> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
__STL_TEMPLATE_NULL struct __type_traits<int> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
__STL_TEMPLATE_NULL struct __type_traits<unsigned int> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
__STL_TEMPLATE_NULL struct __type_traits<long> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
__STL_TEMPLATE_NULL struct __type_traits<unsigned long> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
#ifdef __STL_LONG_LONG
__STL_TEMPLATE_NULL struct __type_traits<long long> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
__STL_TEMPLATE_NULL struct __type_traits<unsigned long long> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
#endif
__STL_TEMPLATE_NULL struct __type_traits<float> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
__STL_TEMPLATE_NULL struct __type_traits<double> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
__STL_TEMPLATE_NULL struct __type_traits<long double> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION
template <class _Tp> struct __type_traits<_Tp*> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type; };
#else
#endif
|
即所有的built-in类型和指针类型均不为 __true_type
, 符合常识,因为这些类型没有析构函数。
其他特化版本_Destroy
1 2 3 4 5 6 7 8 9 10 11
|
inline void _Destroy(char*, char*) {} inline void _Destroy(int*, int*) {} inline void _Destroy(long*, long*) {} inline void _Destroy(float*, float*) {} inline void _Destroy(double*, double*) {} #ifdef __STL_HAS_WCHAR_T inline void _Destroy(wchar_t*, wchar_t*) {} #endif
|
这些常见内置类型做了特化处理,我并未找到合适的理由,为什么要写这些特例,因为即使不写特例化版本也可以跑过,问了下chatgpt
,回答是因为考虑了额外开销。所以暂时也认可这个理由。
3. 总结·
本篇从placement new
出发,引出了stl的构造函数如何实现,之后详细介绍了 destroy
函数族的实现,其中第二个版本的destroy
最为复杂,利用了前文提到的traits
技术。
补充 - new操作符·
除了 placement new 之外,C++ 中还有多种 new 的方式,其中比较常用的包括:
- 普通的 new,用于在堆上分配内存,并调用构造函数初始化对象。
- new[],用于在堆上分配一段连续的内存,并调用构造函数初始化对象数组。
- operator new,用于在堆上分配内存,但不调用构造函数。
- operator new[],用于在堆上分配一段连续的内存,但不调用构造函数。
- nothrow new,用于在堆上分配内存,如果分配失败则返回空指针而不是抛出异常。
- Placement new,用于在已有的内存上调用构造函数来初始化对象。
需要注意的是,对于 new 和 new[] 分配的内存,需要使用对应的 delete 和 delete[] 释放内存。而对于 placement new,由于没有进行内存分配,因此不需要使用 delete 或 delete[] 释放内存,而是需要显式调用析构函数来销毁对象。