C++ STL源码个人学习与分析记录 ——Construct()与Destroy()

  作为一位C++程序员,直接使用STL标准中的各类容器(vector,list,map,hash)以及各种泛型算法完成一些任务是十分正常而又便捷的。但是,只会使用工具而不知道工具的原理和构造方法,那肯定是不可能完全搞懂和掌握手中这些强力的工具的。

  为了搞懂这些STL容器的底层逻辑和代码结构,想找找有没有相关的介绍书籍,找来找去只有这本《STL源码分析》,一看作者,侯捷老师,好嘛,这不是《Effective C++》的中文译者,看来又可以享受和领略侯老师独特的构词造句了,哈哈哈。

1. 目前所使用的编译器

1.1 编译器:MinGW Version:13.2.0

  MinGW(Minimalist GNU for Windows) 是一个用于 Windows 平台的开发工具集,它提供了一组 GNU 工具和库,可以用于编译和构建本地的 Windows 应用程序。MinGW 的目标是在 Windows 环境下提供类似于 Unix/Linux 环境下的开发工具,使开发者能够轻松地在 Windows 上编写和编译 C、C++ 等程序。

1.2 MinGW的主要组件

  • 1) GCC(GNU Compiler Collection): GCC 是一个开源的编译器套件,支持多种编程语言,包括 C、C++、Fortran 等。在 MinGW 中,GCC 被用来编译和生成 Windows 平台下的可执行文件。

  • 2) Binutils:Binutils 是一组用于处理二进制文件的工具,包括汇编器、链接器、目标文件处理器等。在 MinGW 中,Binutils 用于将编译后的源代码转换为可执行文件。

  • 3 )运行时库(Runtime Libraries): MinGW 提供了 Windows 下所需的 C 和 C++ 运行时库,这些库是在编译和链接时所需要的,以便在 Windows 环境下运行程序。

  • 4)MSYS(Minimal SYStem): MSYS 是一个轻量级的 Unix-like 环境,它在 Windows 上提供了一些基本的 Unix 命令行工具,使开发者能够更方便地使用命令行进行开发和构建。

  MinGW 可以与其他开发工具集(如 Visual Studio)一起使用,但它的重点是提供一个简单的方式来在 Windows 上进行开发,无需依赖复杂的集成开发环境(IDE)。MinGW 的使用可以让开发者更接近标准的开发环境,同时也方便了跨平台的开发。

1.3 写文初衷

  在阅读这本书之前其实存在一定的疑虑,最主要的原因为该书的发行时期,2002年,距我目前已有22年之隔,某呼上对该书的评论中也主要指出该书中的代码已经过时,不应当再阅读此书。但思索再三,目前该本教材的内容正是我所需求的,而书中的那句“源码之下,毫无秘密”也提醒了我,既然书中代码已经过时,那为何不直接阅读已经安装的编译器GCC中的STL源代码文件呢?因此,顺手写一篇博客记录一下阅读源码的过程。

2.构造与析构工具:Construct()与Destory()函数的定义

  书中提醒到,SGI STL版本的Construct()与Destory()函数定义在<stl_construct.h>文件中,搜寻了MinGW的安装空间,也找到同名的文件(相对路径为 “…\MinGW\include\c++\13.2.0\bits”,其中“13.2.0”为我所使用的编译器版本),以下是对应于书中源码的部分代码截取。

2.1 Construct()函数的定义

#if __cplusplus >= 201103L //如果当前使用的C++标准为C++11以及更高标准的话,则可以定义一个可变参数的函数模版
  template<typename _Tp, typename... _Args>
    _GLIBCXX20_CONSTEXPR
    inline void
    _Construct(_Tp* __p, _Args&&... __args) //传入一个指针__p指向已经分配的内存空间,以及一个可变参数_Args
    {
#if __cplusplus >= 202002L
      if (std::__is_constant_evaluated())
	{
	  // Allow std::_Construct to be used in constant expressions.
	  std::construct_at(__p, std::forward<_Args>(__args)...); //如果C++标准为C++20及其以上且当前函数调用的上下文环境为常量时,调用std::construct_at()完成构建工作
	  return;
	}
#endif
      ::new((void*)__p) _Tp(std::forward<_Args>(__args)...);
      //否则就调用全局定位new函数,在指针__p所指内存空间调用类型_Tp的带有初始化器的构造函数实现对象的构造。
    }
#else //如果所使用的C++标准低于C++11,则定义下述的Construct函数,因为可变参数是C++11引入的新特性!!
  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

2.1.1 “__cplusplus”的含义

__cplusplus是一个预定义宏,用来指明当前编译器在使用的C++标准版本,它由编译器自动定义。以下是常见的“__cplusplus”的值

  • C++98/03: 199711L
    该数值对应于原始的 C++98 standard (ISO/IEC 14882:1998) 和它的 2003 revision (C++03).

  • C++11: 201103L
    该值对应于 C++11 standard (ISO/IEC 14882:2011).

  • C++14: 201402L
    该值对应于 C++14 standard (ISO/IEC 14882:2014).

  • C++17: 201703L
    该值对应于 C++17 standard (ISO/IEC 14882:2017).

  • C++20: 202002L
    该值对应于 C++20 standard (ISO/IEC 14882:2020).

  • C++23: 202302L
    该值对应于 C++23 standard ( ISO/IEC 14882:2023).

2.1.2 编译组态

  由于不同的编译器厂商对C++语言特性的支持程度不同,为了使得程序库能够拥有较强的移植能力,不同版本的STL标准库都会准备一个环境组态文件来定义一系列的常量,以表示该组态能否成立。书中给出了具体的案例说明和示例代码,此处就不再赘述。(SGI STL版本的环境组态文件为<stl_config.h>,GCC的组态文件为<c++config.h>,可以在…/bits目录下找到)

例如,像上述代码中的第二行出现了“_GLIBCXX20_CONSTEXPR”,它的含义如下:

  • 1)_GLIBCXX:这个前缀通常在 libstdc++ 中使用,用于定义特定于 GNU C++ 标准库实现的宏和标识符。它有助于防止与用户代码发生命名冲突。

  • 2)20:这个数字指的是 C++20 标准。宏 _GLIBCXX20_CONSTEXPR 专门用于 C++20 中可用的功能。

  • 3)CONSTEXPR: 这是 C++ 11 中引入的 C++ 关键字,代表 “常量表达式”,用于定义可在编译时求值的函数或变量。

2.1.3 (void*)__p

  代码中但凡涉及到调用全局new函数的时候,都会强制将指针的类型转换为(void*),目的是为了显式地指出当前该指针p所指向的内存空间是原始内存空间,与任何数值类型都无关。

2.2 Destroy()函数的定义

Destroy()函数的版本一(比较简单,不多解释)

  /**
   * Destroy the object pointed to by a pointer type.
   */
  template<typename _Tp>
    _GLIBCXX14_CONSTEXPR inline void
    _Destroy(_Tp* __pointer)
    {
#if __cplusplus > 201703L
      std::destroy_at(__pointer);
#else
      __pointer->~_Tp();
#endif
    }

Destroy()函数的版本二: 接受两个迭代器,并析构[__first,__last)范围内的对象

//SGI STL与GNU STL在_Destroy_aux的实现上存在了分歧,
//SGI STL采用的是对函数模版的重载,
//而GNU则是通过定义类模版和对模版进行特化的方式实现了两种版本的Destroy_aux,并且正在发挥作用的是嵌套在Destroy_aux模版中的__destroy的成员函数模板。
  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)); //如果迭代器所指对象拥有非trivial析构函数,则调用版本一的Destroy()函数。
	    //
	}
    };

  template<>
    struct _Destroy_aux<true>
    {
      template<typename _ForwardIterator>
        static void
        __destroy(_ForwardIterator, _ForwardIterator) { }
    };

  /**
   * 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; //利用迭代器的trait模版,编译期间获得当前迭代器所指对象的类型
#if __cplusplus >= 201103L
      // A deleted destructor is trivial, this ensures we reject such types: 确保迭代器所指对象的析构函数没有被delete
      static_assert(is_destructible<_Value_type>::value,
		    "value type is destructible");
#endif
#if __cplusplus >= 202002L
      if (std::__is_constant_evaluated())
	return std::_Destroy_aux<false>::__destroy(__first, __last);
#endif
//根据当前迭代器所指对象类型是否具有trivial析构函数来抉择是调用普通版本的_Destroy_aux类的成员函数__destroy,还是调用特化版本的
//_Destroy_aux<true>类的__destroy()成员函数。
      std::_Destroy_aux<__has_trivial_destructor(_Value_type)>:: 
	__destroy(__first, __last);
    }

3. 总结

  从上述代码的分析中可以看出,两个版本的STL的Construct()与Destory()基本工具的定义在底层实现上大差不差,Construct()工具主要是在已分配的空间中利用定位New运算符,结合所给初值调用数值类型的构造函数完成对象的构造。由于C++11引入了可变参数模版,所以Construct()的函数参数做了一些调整,原本的左值引用变为了模版参数包(Template Parameter Pack)。Destory()工具则是根据迭代器所指对象是否拥有trivial析构函数来决定是否通过调用对象的析构函数完成销毁任务,或是什么也不做。

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值