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_style和string对象所指的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函数。