引言
一直以来广大C++使用者对标准库中std::string褒贬不一,笔者整理了一下,大致是以下几点
- 不支持一些常用功能,例如format
- 有algorithm的情况下.basic_string支持的功能太多,过于冗余,
- 缺少编码信息,对于宽字节等有其它的容器,比如wstring,u16string,u32string.
- 不少地方效率不够
- 比起字符串更应该叫字节串,它并不像其他语言一样只读的(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的体系结构部分