SGI STL中string的源码解读(2)

来源:互联网 发布:mac pro 需要买鼠标吗 编辑:程序博客网 时间:2024/05/22 11:38

5. Basic_string的构造函数和析构函数

在看Basic_string构造函数之前先看一下string中对应的成员变量。在basic_string<>中定义了一个辅助的存储结构Alloc_hider(继承于Allocator),该结构仅仅简单的封装了真实的数据即char* mPointer;在basic_string<>中直接用Alloc_hider定义一个数据成员mDataPlus(即对应char* mPointer)。其实mPointer就是上面图中__P所指的位置。

SGI STL重载了众多的构造函数,分别如下:

构造函数用到的一个重要函数_S_construct:

_S_construct是一组重载的函数,主要是区别具有不同类型的的迭代器,提高效率。

template<class _InIterator>

static _CharT*    _S_construct(_InIterator __beg, _InIterator __end,const _Alloc& __a)

{

    typedef typename _Is_integer<_InIterator>::_Integral _Integral;

    return _S_construct_aux(__beg, __end, __a, _Integral());

}

_S_construct_aux是一个辅助函数,当_Integral()为假,即迭代器不是整数性质,会根据迭代器的类型标志分别调用下面最后一个参数为input_iterator_tag或者forward_iterator_tag的_S_construct函数,当_Integral()为真,调用只有三个参数的_S_construct函数。

1. 迭代器属于输入迭代器

// For Input Iterators, used in istreambuf_iterators, etc.

template<class _InIterator>

static _CharT*    _S_construct(_InIterator __beg, _InIterator __end,const _Alloc& __a, input_iterator_tag)

{

if (__beg == __end && __a == _Alloc())

return _S_empty_rep()._M_refdata();

// Avoid reallocation for common case.

_CharT __buf[128];

size_type __len = 0;

while (__beg != __end && __len < sizeof(__buf) / sizeof(_CharT))

{

__buf[__len++] = *__beg;

++__beg;

}

//预先建立存储128个字符的空间

_Rep* __r = _Rep::_S_create(__len, size_type(0), __a);

traits_type::copy(__r->_M_refdata(), __buf, __len);

try

{

while (__beg != __end)

{

//If为真,表示预定义的长度不够,需要增加分配空间

if (__len == __r->_M_capacity)

{

_Rep* __another = _Rep::_S_create(__len + 1, __len, __a);

traits_type::copy(__another->_M_refdata(),__r->_M_refdata(), __len);

__r->_M_destroy(__a);

__r = __another;

}

__r->_M_refdata()[__len++] = *__beg;

++__beg;

}

}

catch(...)

{

__r->_M_destroy(__a);

__throw_exception_again;

}

__r->_M_length = __len;

__r->_M_refdata()[__len] = _Rep::_S_terminal;       // grrr.

return __r->_M_refdata();

}

2. 迭代器属于输入输出迭代器

// For forward_iterators up to random_access_iterators, used for

// string::iterator, _CharT*, etc.

template<class _FwdIterator>

static _CharT*    _S_construct(_FwdIterator __beg, _FwdIterator __end, const _Alloc& __a, forward_iterator_tag)

{

if (__beg == __end && __a == _Alloc())

return _S_empty_rep()._M_refdata();

 

// NB: Not required, but considered best practice.

if (__builtin_expect(__is_null_pointer(__beg), 0))

__throw_logic_error(__N("basic_string::_S_construct NULL not valid"));

 

const size_type __dnew = static_cast<size_type>(std::distance(__beg,__end));

// Check for out_of_range and length_error exceptions.

//直接根据已知数据长度分配字符串的空间

_Rep* __r = _Rep::_S_create(__dnew, size_type(0), __a);

try

{

_S_copy_chars(__r->_M_refdata(), __beg, __end);

}

catch(...)

{

__r->_M_destroy(__a);

__throw_exception_again;

}

__r->_M_length = __dnew;

__r->_M_refdata()[__dnew] = _Rep::_S_terminal;  // grrr.

return __r->_M_refdata();

}

3. 迭代器属于随机迭代器

static _CharT*    _S_construct(size_type __n, _CharT __c, const_Alloc& __a)

{

if (__n == 0 && __a == _Alloc())

return _S_empty_rep()._M_refdata();

 

// Check for out_of_range and length_error exceptions.

//直接根据输入的长度分配空间

_Rep* __r = _Rep::_S_create(__n, size_type(0), __a);

if (__n)

traits_type::assign(__r->_M_refdata(), __n, __c);

 

__r->_M_length = __n;

__r->_M_refdata()[__n] = _Rep::_S_terminal;  // grrr

return __r->_M_refdata();

}

显然SGI STL提供的这三种方式效率是不同的,方法1中STL是通过猜测每次分配128个字节,有可能浪费也有可能不足,不足的时候只能再次分配,方法2是根据迭代器的性质计算出距离然后分配空间,方法3则直接根据距离数据分配空间。

缺省构造函数:

template<typename _CharT, typename _Traits, typename _Alloc>

inline basic_string<_CharT, _Traits, _Alloc>::

basic_string()

: _M_dataplus(_S_empty_rep()._M_refdata(), _Alloc()) { }

其中_S_empty_rep()返回静态数组的地址,_M_refdata()返回的下一个地址(this + 1),并传递对应的Allocator分配内存。

由此可以看出,所有用缺省构造函数定义的string对象都是使用空串。

空串有分配器的构造函数:

template<typename _CharT, typename _Traits, typename _Alloc>

basic_string<_CharT, _Traits, _Alloc>::

basic_string(const _Alloc& __a)

    : _M_dataplus(_S_construct(size_type(), _CharT(), __a), __a)

{ }

临时对象size_type()实际上是0,仍然返回的是空串。

Copy构造函数:

basic_string<_CharT, _Traits, _Alloc>::

basic_string(const basic_string& __str)

: _M_dataplus(__str._M_rep()->_M_grab(_Alloc(__str.get_allocator()), __str.get_allocator()), __str.get_allocator())

{ }

这个非常简单,就是实质就是增加了引用计数。

从一个string构造子串:

template<typename _CharT, typename _Traits, typename _Alloc>

basic_string<_CharT, _Traits, _Alloc>::

basic_string(const basic_string& __str, size_type __pos, size_type __n)

: _M_dataplus(_S_construct(__str._M_data()+ __str._M_check(__pos, "basic_string::basic_string"),__str._M_data() + __str._M_limit(__pos, __n)+ __pos, _Alloc()), _Alloc())

{ }

_M_check(__pos,"basic_string::basic_string")检查是否越界,如果越界抛出字符串异常,否则返回__pos;_M_data()返回原有字符串;_M_limit(__pos, __n)完成长度检测,即__pos + n的距离不应该超过原字符串的长度,值得注意的是没有对__pos的合法性做检查。

因此该函数直接根据原有字符串新建一个串。注意这个串虽然和原串有字符相同,但他们并没有共享存储的。(当然也有实现了共享子串的方法)。

STL中还有一个使用指定分配器的构造函数,除了最后关于分配器的参数其余都相同。此外还提供了从char*到string的构造函数,根据两个迭代器构造string等构造函数,都比较简单,直接调用上面的_S_construct函数完成。

析构函数:

最后简单的看一下析构函数,因为非常简单,所以放在这里没有单独作为一个大一点的标题。

~basic_string()

{

_M_rep()->_M_dispose(this->get_allocator());

}

在这里就是释放空间。

6.           赋值构造函数operator=

operator=中用到的一个重要函数assign:

assign在STL定义成为6个重载函数。其中有两个非常关键,其余介在此基础上实现。

1. 从string对象赋值

basic_string&

assign(const basic_string& __str)

{

if (_M_rep() != __str._M_rep())

{

//如果两个字符串不相同,则根据引用计数规则发生计数

const allocator_type __a = this->get_allocator();

_CharT* __tmp = __str._M_rep()->_M_grab(__a, __str.get_allocator());

//释放原来计数对象

_M_rep()->_M_dispose(__a);

//把自己的字符指针指向共享的字符串

_M_data(__tmp);

}

return *this;

}

2. 从c-style的字符串对象赋值

basic_string&

assign(const _CharT* __s, size_type __n)

{

    //这是一个宏,判断字符串不为NULL,长度n不为0;

__glibcxx_requires_string_len(__s, __n);

if (__n > this->max_size())

__throw_length_error(__N("basic_string::assign"));

    //这个if判断非常有意思,显然当我们发现这个string对象共享的是否,在赋值的时候必须小心,因为有多个对象指向同一个c_style的字符串;

    //对于第二个和第三个判断我还没有弄清楚到底防止哪些情况;假单的说就是要着这两个字符串不重叠,当然了字符串重叠可能需要单独处理

    //单纯丛代码上分析显然是为了警戒c_stylestring对象所指的c_style字符串相连

    //我的唯一的猜测是这两个判断语句为了保护string对象中的结构体Rep_base,防止因为赋值修改了该结构体(也许有人进行了类型转换,然后赋值,那么可能造成程序当掉)

    //在下面会分析replace_safe的代码

if (_M_rep()->_M_is_shared() || less<const _CharT*>()(__s, _M_data()) || less<const _CharT*>()(_M_data() + this->size(), __s))

return _M_replace_safe(size_type(0), this->size(), __s, __n);

else

{

//判断c_style的字符串是否重叠,进行内存copy或者move

const size_type __pos = __s - _M_data();

if (__pos >= __n)

traits_type::copy(_M_data(), __s, __n);

else if (__pos)

traits_type::move(_M_data(), __s, __n);

_M_rep()->_M_set_sharable();

_M_rep()->_M_length = __n;

_M_data()[__n] = _Rep::_S_terminal;  // grr.

return *this;

}

}

还有4个重载的函数,有两个非常简单,有两个使用了replace函数,下面会讨论到。

有了assign()函数,operator=就非常容易实现了,直接根据参数调用相应的assign函数。