std::string源码剖析(1) 体系结构

引言

一直以来广大C++使用者对标准库中std::string褒贬不一,笔者整理了一下,大致是以下几点

  1. 不支持一些常用功能,例如format
  2. 有algorithm的情况下.basic_string支持的功能太多,过于冗余,
  3. 缺少编码信息,对于宽字节等有其它的容器,比如wstring,u16string,u32string.
  4. 不少地方效率不够
  5. 比起字符串更应该叫字节串,它并不像其他语言一样只读的(string_view).

https://www.zhihu.com/search?type=content&q=std%3A%3Astring

种种原因使得我的内心充满了疑惑,所以决定欣赏下库的代码,我们开始吧

我们首先来看看string到底是什么

  template<class _CharT>
    struct char_traits;

  template<> struct char_traits<char>;

#ifdef _GLIBCXX_USE_WCHAR_T
  template<> struct char_traits<wchar_t>;
#endif

#if ((__cplusplus >= 201103L) \
     && defined(_GLIBCXX_USE_C99_STDINT_TR1))
  template<> struct char_traits<char16_t>;
  template<> struct char_traits<char32_t>;
#endif

_GLIBCXX_BEGIN_NAMESPACE_CXX11

  template<typename _CharT, typename _Traits = char_traits<_CharT>,
           typename _Alloc = allocator<_CharT> >
    class basic_string;

  /// A string of @c char
  typedef basic_string<char>    string;   

#ifdef _GLIBCXX_USE_WCHAR_T
  /// A string of @c wchar_t
  typedef basic_string<wchar_t> wstring;   
#endif

#if ((__cplusplus >= 201103L) \
     && defined(_GLIBCXX_USE_C99_STDINT_TR1))
  /// A string of @c char16_t
  typedef basic_string<char16_t> u16string; 

  /// A string of @c char32_t
  typedef basic_string<char32_t> u32string; 
#endif

我们终于看到了string的真面目,其实string不是一个单独的类 它只不过是basic_string这个模板的一个实例而已,

我们再来看看basic_string是什么

  template<typename _CharT, typename _Traits, typename _Alloc>
    class basic_string{
    ...
    }

我们可以看到basic_string模板有三个参数 一个是前面提到的类型,一个是Traits,一个是Alloc
我们一个一个来说 首先说说Traits,我们可以看到在上面的第一栏代码中给Traits的类型的char_traits,那么这究竟是什么呢,我们来看看char_traits的声明

  template<typename _CharT>
    struct char_traits
    {
      typedef _CharT                                    char_type;
      typedef typename _Char_types<_CharT>::int_type    int_type;
      typedef typename _Char_types<_CharT>::pos_type    pos_type;
      typedef typename _Char_types<_CharT>::off_type    off_type;
      typedef typename _Char_types<_CharT>::state_type  state_type;
      ...}

我们看到其实就是一些类型的声明 剩下的函数就是一些简单的字符操作

我们再来看看char_traits< char>

  template<>
    struct char_traits<char>
    {
      typedef char              char_type;
      typedef int               int_type;
      typedef streampos         pos_type;
      typedef streamoff         off_type;
      typedef mbstate_t         state_type;

      static _GLIBCXX17_CONSTEXPR void
      assign(char_type& __c1, const char_type& __c2) _GLIBCXX_NOEXCEPT
      { __c1 = __c2; }

      static _GLIBCXX_CONSTEXPR bool
      eq(const char_type& __c1, const char_type& __c2) _GLIBCXX_NOEXCEPT
      { return __c1 == __c2; }
      
      .....
      }

其实char类型就是一个全特化 其实不仅是char类型的有特化 char16_t,char32_t等也都有 至于为什么要给不同的类型有特化 原因就是比较中的memcmp函数使用了不同的版本.

到这里我们算是清楚了三个模板参数中一个的含义,就是char_traits,其作用就是对简单的低级字符串操作进行封装

其实剩下的参数就不必多说,一个是必要的类型,一个是alloctor.

我们来细细看看basic_string的内部数据

  template<typename _CharT, typename _Traits, typename _Alloc>
    class basic_string
    {
      typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
	rebind<_CharT>::other _Char_alloc_type;
      typedef __gnu_cxx::__alloc_traits<_Char_alloc_type> _Alloc_traits;

      // Types:
    public:
      typedef _Traits					traits_type;
      typedef typename _Traits::char_type		value_type;
      typedef _Char_alloc_type				allocator_type;
      typedef typename _Alloc_traits::size_type		size_type;
      typedef typename _Alloc_traits::difference_type	difference_type;
      typedef typename _Alloc_traits::reference		reference;
      typedef typename _Alloc_traits::const_reference	const_reference;
      typedef typename _Alloc_traits::pointer		pointer;
      typedef typename _Alloc_traits::const_pointer	const_pointer;
      typedef __gnu_cxx::__normal_iterator<pointer, basic_string>  iterator;
      typedef __gnu_cxx::__normal_iterator<const_pointer, basic_string>
							const_iterator;
      typedef std::reverse_iterator<const_iterator>	const_reverse_iterator;
      typedef std::reverse_iterator<iterator>		reverse_iterator;

      ///  Value returned by various member functions when they fail.
      static const size_type	npos = static_cast<size_type>(-1);
      
      ......
      
      struct _Alloc_hider : allocator_type // TODO check __is_final
      {
#if __cplusplus < 201103L
	_Alloc_hider(pointer __dat, const _Alloc& __a = _Alloc())
	: allocator_type(__a), _M_p(__dat) { }
#else
	_Alloc_hider(pointer __dat, const _Alloc& __a)
	: allocator_type(__a), _M_p(__dat) { }

	_Alloc_hider(pointer __dat, _Alloc&& __a = _Alloc())
	: allocator_type(std::move(__a)), _M_p(__dat) { }
#endif

	pointer _M_p; // The actual data.
      };

      _Alloc_hider	_M_dataplus;
      size_type		_M_string_length;

      enum { _S_local_capacity = 15 / sizeof(_CharT) };

      union
      {
	_CharT           _M_local_buf[_S_local_capacity + 1];
	size_type        _M_allocated_capacity;
	}
	......
};

其实自npos 前面的类型声明简单的了解的话大可不必深究,因为其应用都是有场景的,用的时候自会出现,没有必有把这个过程反过来去学习,

首先我们看第一个数据 _M_dataplus 这个其实就是一个数据的封装,把alloctor和string的源数据存放在一起,方便管理,在C++11后支持了右值,

第二个参数就是 _M_string_length 这个也很好理解,就是当前数据的有效长度,因为指针是从alloctor分配来的,数据不一定会把所有的数据域占满

第三个参数就比较有意思了 _S_local_capacity 本地容量? 奇怪的名字,我们的数据不都是从alloctor中来的吗,为什么会出现一个本地容量, 这就是string的核心之一 就是短字符串优化,我们可以看看下一个数据,一个联合体,其中有一个参数我们要多加注意,_M_local_buf[_S_local_capacity + 1],奇怪,为什么string中会有一个静态数组呢,其实这就是短字符串优化,当string中的数据较小时,把数据存储在静态数组中,alloctor就不必进行一次内存分配,从而减少一次系统调用,减少一次用户态向内核态的转换,这就是短字符串优化,

最后再来看看 _M_allocated_capacity 这个参数,其含义就是容量,也就是分配的内存大小,很巧妙的设计,当使用静态内存的时候,容量为15,不用计算,当大于15的时候静态数组便没有用了,正好用来记录容量,至于如何区分使用的内存是静态的还是由alloctor分配的,就是我们的_M_dataplus要做的事了,其中有一个指针,指向数据,我们只需要在进行短字符串优化时把指针指向静态数组即可

以上就是string的体系结构部分

你可能感兴趣的:(c++语法)