《STL源码分析》学习笔记 — 空间配置器 — 构造和析构基本工具
- 一、文档注释
- 二、construct_at 和 destroy_at
- 三、_Construct 和 普通 _Destroy
- 四、辅助类 _Destroy_aux 和 _Destroy_n_aux
- 五、基于范围析构的 _Destroy 和 _Destroy_n
- 六、destroy 和 destroy_n
正如我们前面所学习过的,在标准空间配置器中, construct 和 destroy 方法已经被最新的C++标准移除。这也符合模块化的思想。空间的管理来就分为两组功能:内存的申请与释放;对象的构造和析构。在C++20中,标准 allocator 的功能已经被简化到(或者说规范到)仅负责内存的申请与释放。对象的构造和析构则是由 stl_construct.h 中的函数进行管理。
一、文档注释
先看看该文档的注释:
/* This file provides the C++17 functions std::destroy_at, std::destroy, and
* std::destroy_n, and the C++20 function std::construct_at.
* It also provides std::_Construct, std::_Destroy,and std::_Destroy_n functions
* which are defined in all standard modes and so can be used in C++98-14 code.
* The _Destroy functions will dispatch to destroy_at during constant
* evaluation, because calls to that function are intercepted by the compiler
* to allow use in constant expressions.
*/
这个文件提供了用于C++17的 destroy_at、destroy、destroy_n,用于C++20的 construct_at(不知道为什么和前面的不是一个版本推出的)。它同样提供了 _Construct、_Destroy、_Destroy_n 函数,它们在所有标准模式中被定义,因此可以被用于 C++98-14 的代码。在常量表达式中,_Destroy 函数将被转发到 destroy_at方法中,因为在编译器中将会截断在常量表达式中对该方法的调用。
二、construct_at 和 destroy_at
#if __cplusplus >= 201703L
template <typename _Tp>
_GLIBCXX20_CONSTEXPR inline void destroy_at(_Tp* __location)
{
if constexpr (__cplusplus > 201703L && is_array_v<_Tp>)
{
for (auto& __x : *__location)
std::destroy_at(std::__addressof(__x));
}
else
__location->~_Tp();
}
#if __cplusplus > 201703L
template<typename _Tp, typename... _Args>
constexpr auto construct_at(_Tp* __location, _Args&&... __args)
noexcept(noexcept(::new((void*)0) _Tp(std::declval<_Args>()...)))-> decltype(::new((void*)0) _Tp(std::declval<_Args>()...))
{ return ::new((void*)__location) _Tp(std::forward<_Args>(__args)...); }
#endif // C++20
#endif// C++17
这里,我们可以看到 construct_at 函数仅仅是通过定位 new 运算符进行了参数转发。因此这个函数只能用来构造单个对象(想来也合理,如果可以构造数组,那么参数传递就会很麻烦)。此函数有一个返回值,为指向模板参数类型的指针。
destroy_at 函数通过 is_array_v 判断类型参数是否为数组,以决定调用多少次析构函数。那么很明显,只有当参数 __location 为一个数组指针时,才会进入第一个判断。使用 __addressof 是为了防止模板类型的 & 被重载。由于我们使用的是指针类型参数调用析构函数,因此也可以支持多态。
三、_Construct 和 普通 _Destroy
/**
* Constructs an object in existing memory by invoking an allocated
* object's constructor with an initializer.
*/
#if __cplusplus >= 201103L
template<typename _Tp, typename... _Args>
inline void _Construct(_Tp* __p, _Args&&... __args)
{ ::new(static_cast<void*>(__p)) _Tp(std::forward<_Args>(__args)...); }
#else
template<typename _T1, typename _T2>
inline void _Construct(_T1* __p, const _T2& __value)
{
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 402. wrong new expression in [some_]allocator::construct
::new(static_cast<void*>(__p)) _T1(__value);
}
#endif
template<typename _T1>
inline void _Construct_novalue(_T1* __p)
{ ::new(static_cast<void*>(__p)) _T1; }
template<typename _Tp>
_GLIBCXX14_CONSTEXPR inline void _Destroy(_Tp* __pointer)
{
#if __cplusplus > 201703L
std::destroy_at(__pointer);
#else
__pointer->~_Tp();
#endif
}
这几个函数为C++内部使用的普通构造和析构函数(为了兼容老版本)。第一个 _Construct 函数通过定位 new 运算符实现构造(在C++11之后可以使用参数转发,因此支持多个参数的构造;在C++11之前,只支持单一参数的构造); 第二个 _Construct 函数是无参版本的构造(默认构造函数)。这两个构造函数和前面的构造函数的区别是没有返回值。_Destroy 的作用就是析构函数调用的一层封装。
四、辅助类 _Destroy_aux 和 _Destroy_n_aux
template<bool>
struct _Destroy_aux
{
template<typename _ForwardIterator>
static _GLIBCXX20_CONSTEXPR void __destroy(_ForwardIterator __first, _ForwardIterator __last)
{
for (; __first != __last; ++__first)
std::_Destroy(std::__addressof(*__first));
}
};
template<>
struct _Destroy_aux<true>
{
template<typename _ForwardIterator>
static void __destroy(_ForwardIterator, _ForwardIterator) { }
};
_Destroy_aux 是一个辅助类,用于析构某两个迭代器范围内的对象;当其类型参数为 false 时,才会真正的执行析构工作。当其为 true 时,内部的 __destroy 函数什么都不做。注意这里迭代器的类型要求为前向迭代器。不难推测,这个模板参数的作用大概率是是否有 non-trivial 的析构函数。
template<bool>
struct _Destroy_n_aux
{
template<typename _ForwardIterator, typename _Size>
static _GLIBCXX20_CONSTEXPR _ForwardIterator __destroy_n(_ForwardIterator __first, _Size __count)
{
for (; __count > 0; (void)++__first, --__count)
std::_Destroy(std::__addressof(*__first));
return __first;
}
};
template<>
struct _Destroy_n_aux<true>
{
template<typename _ForwardIterator, typename _Size>
static _ForwardIterator __destroy_n(_ForwardIterator __first, _Size __count)
{
std::advance(__first, __count);
return __first;
}
};
_Destroy_n_aux 同样是一个辅助类,功能与前面的 _Destroy_n 类似。不过该结构体指定析构范围的参数变为了起始迭代器和范围长度。该结构体中提供的 __destroy_n 函数返回了一个迭代器,指向了最后一个被释放元素的下一个元素。
五、基于范围析构的 _Destroy 和 _Destroy_n
/**
* Destroy a range of objects. If the value_type of the object has
* a trivial destructor, the compiler should optimize all of this
* away, otherwise the objects' destructors must be invoked.
*/
template<typename _ForwardIterator>
_GLIBCXX20_CONSTEXPR inline void _Destroy(_ForwardIterator __first, _ForwardIterator __last)
{
typedef typename iterator_traits<_ForwardIterator>::value_type
_Value_type;
#if __cplusplus >= 201103L
// A deleted destructor is trivial, this ensures we reject such types:
static_assert(is_destructible<_Value_type>::value,
"value type is destructible");
#endif
#if __cplusplus > 201703L && defined __cpp_lib_is_constant_evaluated
if (std::is_constant_evaluated())
return _Destroy_aux<false>::__destroy(__first, __last);
#endif
std::_Destroy_aux<__has_trivial_destructor(_Value_type)>::
__destroy(__first, __last);
}
/**
* Destroy a range of objects. If the value_type of the object has
* a trivial destructor, the compiler should optimize all of this
* away, otherwise the objects' destructors must be invoked.
*/
template<typename _ForwardIterator, typename _Size>
_GLIBCXX20_CONSTEXPR inline _ForwardIterator _Destroy_n(_ForwardIterator __first, _Size __count)
{
typedef typename iterator_traits<_ForwardIterator>::value_type
_Value_type;
#if __cplusplus >= 201103L
// A deleted destructor is trivial, this ensures we reject such types:
static_assert(is_destructible<_Value_type>::value,
"value type is destructible");
#endif
#if __cplusplus > 201703L && defined __cpp_lib_is_constant_evaluated
if (std::is_constant_evaluated())
return _Destroy_n_aux<false>::__destroy_n(__first, __count);
#endif
return std::_Destroy_n_aux<__has_trivial_destructor(_Value_type)>::
__destroy_n(__first, __count);
}
_Destroy 函数是根据迭代器范围的析构,借助 _Destroy_aux 实现。注释中提到,如果对象的析构函数为 trivial,那么这种调用应该被编译器优化掉,否则其析构函数将会被调用。这个优化是通过 __has_trivial_destructor实现的。需要注意这个优化仅在非常量表达式中发生。如果 is_constant_evaluated() 为真,则无论如何都将调用析构函数。但是常量表达式本身就是编译时计算的,而我们所希望的优化是运行时不调用没有意义的代码。因此这样的实现也是没有问题的。
类似地,_Destroy_n 借助 _Destroy_n_aux 实现了从某个迭代器开始的多个对象的析构。
六、destroy 和 destroy_n
#if __cplusplus >= 201703L
template <typename _ForwardIterator>
_GLIBCXX20_CONSTEXPR inline void destroy(_ForwardIterator __first, _ForwardIterator __last)
{
std::_Destroy(__first, __last);
}
template <typename _ForwardIterator, typename _Size>
_GLIBCXX20_CONSTEXPR inline _ForwardIterator destroy_n(_ForwardIterator __first, _Size __count)
{
return std::_Destroy_n(__first, __count);
}
#endif // C++17
这两个函数是C++17中提供给用户使用的范围析构函数,其返回值为指向最后一个析构元素的下一个元素的迭代器。