《STL源码分析》学习笔记 — 空间配置器 — 构造和析构基本工具

news/2024/7/20 13:31:54 标签: c++, 指针, 内存管理, stl

《STL源码分析》学习笔记 — 空间配置器 — 构造和析构基本工具

  • 一、文档注释
  • 二、construct_at 和 destroy_at
  • 三、_Construct 和 普通 _Destroy
  • 四、辅助类 _Destroy_aux 和 _Destroy_n_aux
  • 五、基于范围析构的 _Destroy 和 _Destroy_n
  • 六、destroy 和 destroy_n

正如我们前面所学习过的,在标准空间配置器中, constructdestroy 方法已经被最新的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_atdestroydestroy_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中提供给用户使用的范围析构函数,其返回值为指向最后一个析构元素的下一个元素的迭代器。


http://www.niftyadmin.cn/n/1340386.html

相关文章

python 在控制台中用不同颜色打印信息

echo "\033[41;36m something here \033[0m" 其中41的位置代表底色, 36的位置是代表字的颜色 转载于:https://blog.51cto.com/9425473/1694466

《STL源码分析》学习笔记 — 空间配置器 — 内存基本处理工具

《STL源码分析》学习笔记 — 空间配置器 — 内存基本处理工具一、uninitialized_copy二、uninitialized_fill三、uninitialized_fill_n四、stl_uninitialized.h 中的扩展内部函数五、stl_uninitialized.h 中的扩展外部函数stl_uninitialized.h 文件中提供了用于处理未初始化内存…

《数据结构、算法与应用 —— C++语言描述》学习笔记 — 队列

《数据结构、算法与应用 —— C语言描述》学习笔记 — 队列一、抽象数据类型二、数组描述1、描述2、类arrayQueue定义3、类arrayQueue实现&#xff08;1&#xff09;构造、析构&#xff08;2&#xff09;容量&#xff08;3&#xff09;元素获取&#xff08;4&#xff09;元素移…

中控考勤机SDK使用中员工姓名的处理( c# )

公司使用的考勤机是中控的指纹考勤机&#xff0c;但是中控的型号乱七八糟&#xff0c;通过程序读出来的型号和实际标的型号不一致。 另外&#xff0c;提供的开发包的C#版本的Demo中调用 axCZKEM1.ReadAllUserID(iMachineNumber); 后调用 axCZKEM1.SSR_GetAllUserInfo(iMachineN…

《数据结构、算法与应用 —— C++语言描述》学习笔记 — 队列 —— 应用(一)

《数据结构、算法与应用 —— C语言描述》学习笔记 — 队列 —— 应用&#xff08;一&#xff09;一、列车车厢重排1、求解策略2、实现二、电路布线1、求解策略2、抽象类Path3、实现4、界面逻辑修改5、效果一、列车车厢重排 在学习栈时&#xff0c;我们考察过这个问题。这次&a…

《More Effictive C++》学习笔记 — 技术(四)

《More Effictive C》学习笔记 — 技术&#xff08;四&#xff09;条款29 —— 引用计数&#xff08;二&#xff09;1、把引用计数加到既有的类上&#xff08;1&#xff09;CLS_RCIPtr&#xff08;2&#xff09;CLS_RCIPtr 的使用2、评估3、同值数据共享&#xff08;个人补充&a…

Sybase自增字段跳号的解决方法

Sybase自增字段跳号原因及影响&#xff1a; 在Sybase数据库中如果数据库在开启的情况下&#xff0c;因为非正常的原因&#xff08;死机、断电&#xff09;而导致数据库服务进程强制结束。 那么自动增长的字段将会产生跳号的情况&#xff0c;再往数据表里面插入记录时&#xff0…

《STL源码分析》学习笔记 — 空间配置器 — allocator_traits

《STL源码分析》学习笔记 — 空间配置器 — allocator_traits一、traits编程技法二、基类 __allocator_traits_base三、allocator_traits1、类型及属性定义&#xff08;1&#xff09;__detected_or_t&#xff08;2&#xff09;_Ptr _Diff _Size2、外部方法3、内部方法四、alloc…