我們平時使用C++開發(fā)過程中或多或少都會使用std::string,但您了解string具體是如何實現(xiàn)的嗎,這里程序喵給大家從源碼角度分析一下。
讀完本文相信您可以回答以下問題:
▼ string的常見的實現(xiàn)方式有幾種?
▼ string類的內(nèi)部結(jié)構(gòu)是什么樣子?
▼ string內(nèi)部使用的內(nèi)存是如何分配管理的?
▼ string是如何拷貝構(gòu)造,如何析構(gòu)的,有引用計數(shù)的概念嗎?
▼ string的data()和c_str()函數(shù)有什么區(qū)別?
▼ std::to_string()是如何實現(xiàn)的?
常見的string實現(xiàn)方式有兩種,一種是深拷貝的方式,一種是COW(copy on write)寫時拷貝方式,以前多數(shù)使用COW方式,但由于目前多線程使用越來越多,COW技術(shù)在多線程中會有額外的性能惡化,所以現(xiàn)在多數(shù)使用深拷貝的方式,但了解COW的技術(shù)實現(xiàn)還是很有必要的。
這里會對這兩種方式都進行源碼分析,正文內(nèi)容較少,更多內(nèi)容都在源碼的注釋中。
string的內(nèi)容主要在gcc源碼的三個文件中:<string>、<basic_string.h>、<basic_string.tcc>
在分析前先介紹下string或者C++ stl中幾個基本的概念:
size: 表示真實數(shù)據(jù)的大小,一般resize函數(shù)改變的就是這個值。
capacity:表示內(nèi)部實際已經(jīng)分配的內(nèi)存大小,capacity一定大于等于size,當(dāng)size超過這個容量時會觸發(fā)重新分配機制,一般reserve函數(shù)改變的就是這個值。
深拷貝下string的實現(xiàn)
<string>文件中有如下代碼:
// file: string
using string = basic_string<char>;
這里可以看到string其實真實的樣子是basic_string,這里可以看下basic_string真實的結(jié)構(gòu):
template <typename _CharT, typename _Traits, typename _Alloc>
class basic_string {
// Use empty-base optimization: http://www.cantrip.org/emptyopt.html
struct _Alloc_hider : allocator_type // TODO check __is_final
{
_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) {}
/**
* _M_p指向?qū)嶋H的數(shù)據(jù)
*/
pointer _M_p; // The actual data.
};
_Alloc_hider _M_dataplus;
/**
* 真實數(shù)據(jù)的長度,等價于前面介紹的STL中的size
*/
size_type _M_string_length;
enum { _S_local_capacity = 15 / sizeof(_CharT) };
/**
* 這里有個小技巧,用了union
* 因為使用_M_local_buf時候不需要關(guān)注_M_allocated_capacity
* 使用_M_allocated_capacity時就不需要關(guān)注_M_local_buf
* 繼續(xù)向下看完您就會明白。
*/
union {
_CharT _M_local_buf[_S_local_capacity + 1];
/**
* 內(nèi)部已經(jīng)分配的內(nèi)存的大小,等價于前面介紹的STL中的capacity
*/
size_type _M_allocated_capacity;
};
};
從這里可以看見整個basic_string的結(jié)構(gòu)如圖:
看下面代碼:
string str;
這段代碼會調(diào)用普通構(gòu)造函數(shù),對應(yīng)的源碼實現(xiàn)如下:
basic_string() : _M_dataplus(_M_local_data()) { _M_set_length(0); }
而_M_local_data()的實現(xiàn)如下:
const_pointer _M_local_data() const {
return std::pointer_traits<const_pointer>::pointer_to(*_M_local_buf);
}
這里可以看見M_dataplus表示實際存放數(shù)據(jù)的地方,當(dāng)string是空的時候,其實就是指向M_local_buf,且_M_string_length是0。
當(dāng)由char*構(gòu)造string時,構(gòu)造函數(shù)如下:
basic_string(const _CharT* __s, size_type __n, const _Alloc& __a = _Alloc()) : _M_dataplus(_M_local_data(), __a) {
_M_construct(__s, __s + __n);
}
首先讓M_dataplus指向local_buf,再看下M_construct的實現(xiàn),具體分析可以看下我代碼中添加的注釋:
/***
* _M_construct有很多種不同的實現(xiàn),不同的迭代器類型有不同的優(yōu)化實現(xiàn),
* 這里我們只需要關(guān)注一種即可,整體思路是相同的。
*/
template <typename _CharT, typename _Traits, typename _Alloc>
template <typename _InIterator>
void basic_string<_CharT, _Traits, _Alloc>::_M_construct(_InIterator __beg, _InIterator __end,
std::input_iterator_tag) {
size_type __len = 0;
size_type __capacity = size_type(_S_local_capacity);
// 現(xiàn)在__capacity是15,注意這個值等會可能會改變
while (__beg != __end && __len < __capacity) {
_M_data()[__len++] = *__beg;
++__beg;
}
/** 現(xiàn)在_M_data()指向的是_M_local_buf
* 上面最多會拷貝_S_local_capacity即15個字節(jié),繼續(xù)往下看,
* 當(dāng)超過_S_local_capacity時會重新申請一塊堆內(nèi)存,_M_data()會去指向這塊新內(nèi)存
*/
__try {
while (__beg != __end) {
if (__len == __capacity) {
/**
* 就是在這里,當(dāng)string內(nèi)capacity不夠容納len個字符時,會使用_M_create去擴容
* 這里你可能會有疑惑,貌似每次while循環(huán)都會去重新使用_M_create來申請多一個字節(jié)的內(nèi)存
* 但其實不是,_M_create的第一個參數(shù)的傳遞方式是引用傳遞,__capacity會在內(nèi)部被修改,稍后會分析
*/
__capacity = __len + 1;
pointer __another = _M_create(__capacity, __len);
/**
* 把舊數(shù)據(jù)拷貝到新的內(nèi)存區(qū)域,_M_data()指向的是舊數(shù)據(jù),__another指向的是新申請的內(nèi)存
*/
this->_S_copy(__another, _M_data(), __len);
/**
* __M_dispose()
* 釋放_M_data()指向的舊數(shù)據(jù)內(nèi)存,如果是_M_local_buf則不需要釋放,稍后分析
*/
_M_dispose();
/**
* _M_data()
* 內(nèi)部的指向內(nèi)存的指針指向這塊新申請的內(nèi)存__another,它的實現(xiàn)其實就是
* void _M_data(pointer __p) { _M_dataplus._M_p = __p; }
*/
_M_data(__another);
/**
* _M_allocated_capacity設(shè)置為__capacity
* 實現(xiàn)為 void _M_capacity(size_type __capacity) { _M_allocated_capacity = __capacity; }
*/
_M_capacity(__capacity);
}
_M_data()[__len++] = *__beg;
++__beg;
}
}
__catch(...) {
/**
* 異常發(fā)生時,避免內(nèi)存泄漏,會釋放掉內(nèi)部申請的內(nèi)存
*/
_M_dispose();
__throw_exception_again;
}
/**
* 最后設(shè)置string的長度為__len
* 實現(xiàn)為void _M_length(size_type __length) { _M_string_length = __length; }
*/
_M_set_length(__len);
}
再分析下內(nèi)部的內(nèi)存申請函數(shù)_M_create:
/**
* @brief _M_create表示申請新內(nèi)存
* @param __capacity 想要申請的內(nèi)存大小,注意這里參數(shù)傳遞方式是引用傳遞,內(nèi)部會改變其值
* @param __old_capacity 以前的內(nèi)存大小
*/
template <typename _CharT, typename _Traits, typename _Alloc>
typename basic_string<_CharT, _Traits, _Alloc>::pointer basic_string<_CharT, _Traits, _Alloc>::_M_create(
size_type& __capacity, size_type __old_capacity) {
/**
* max_size()表示標(biāo)準(zhǔn)庫容器規(guī)定的一次性可以分配到最大內(nèi)存大小
* 當(dāng)想要申請的內(nèi)存大小最大規(guī)定長度時,會拋出異常
*/
if (__capacity > max_size()) std::__throw_length_error(__N("basic_string::_M_create"));
/**
* 這里就是常見的STL動態(tài)擴容機制,其實常見的就是申請為__old_capacity的2倍大小的內(nèi)存,最大只能申請max_size()
* 注釋只是說了常見的內(nèi)存分配大小思想,不全是下面代碼的意思,具體可以直接看下面這幾行代碼哈
*/
if (__capacity > __old_capacity && __capacity < 2 * __old_capacity) {
__capacity = 2 * __old_capacity;
// Never allocate a string bigger than max_size.
if (__capacity > max_size()) __capacity = max_size();
}
/**
* 使用內(nèi)存分配子去分配__capacity+1大小的內(nèi)存,+1是為了多存儲個\0
*/
return _Alloc_traits::allocate(_M_get_allocator(), __capacity + 1);
}
再分析下內(nèi)部的內(nèi)存釋放函數(shù)_M_dispose函數(shù):
/**
* 如果當(dāng)前指向的是本地內(nèi)存那15個字節(jié),則不需要釋放
* 如果不是,則需要使用_M_destroy去釋放其指向的內(nèi)存
*/
void _M_dispose() {
if (!_M_is_local()) _M_destroy(_M_allocated_capacity);
}
/**
* 判斷下當(dāng)前內(nèi)部指向的是不是本地內(nèi)存
* _M_local_data()即返回_M_local_buf的地址
*/
bool _M_is_local() const { return _M_data() == _M_local_data(); }
void _M_destroy(size_type __size) throw() {
_Alloc_traits::deallocate(_M_get_allocator(), _M_data(), __size + 1);
}
再分析下basic_string的拷貝構(gòu)造函數(shù):
/**
* basic_string的拷貝構(gòu)造函數(shù)
* 其實就是每次都做一次深拷貝
*/
basic_string(const basic_string& __str)
: _M_dataplus(_M_local_data(), _Alloc_traits::_S_select_on_copy(__str._M_get_allocator())) {
_M_construct(__str._M_data(), __str._M_data() + __str.length());
}
再分析下basic_string的賦值構(gòu)造函數(shù):
/**
* 賦值構(gòu)造函數(shù),調(diào)用了assign函數(shù)
*/
basic_string& operator=(const basic_string& __str) { return this->assign(__str); }
/**
* 調(diào)用了_M_assign函數(shù)
*/
basic_string& assign(const basic_string& __str) {
this->_M_assign(__str);
return *this;
}
/**
* 賦值的核心函數(shù)
*/
template <typename _CharT, typename _Traits, typename _Alloc>
void basic_string<_CharT, _Traits, _Alloc>::_M_assign(const basic_string& __str) {
if (this != &__str) {
const size_type __rsize = __str.length();
const size_type __capacity = capacity();
/**
* 如果capacity不夠用,需要進行重新分配
*/
if (__rsize > __capacity) {
size_type __new_capacity = __rsize;
pointer __tmp = _M_create(__new_capacity, __capacity);
_M_dispose();
_M_data(__tmp);
_M_capacity(__new_capacity);
}
/**
* 將__str指向的內(nèi)存拷貝到當(dāng)前對象指向的內(nèi)存上
*/
if (__rsize) this->_S_copy(_M_data(), __str._M_data(), __rsize);
_M_set_length(__rsize);
}
}
再分析下移動構(gòu)造函數(shù):
/**
* 移動構(gòu)造函數(shù),其實就是把src指向的內(nèi)存移動到了dst種
*/
basic_string(basic_string&& __str) noexcept : _M_dataplus(_M_local_data(), std::move(__str._M_get_allocator())) {
if (__str._M_is_local()) {
traits_type::copy(_M_local_buf, __str._M_local_buf, _S_local_capacity + 1);
} else {
_M_data(__str._M_data());
_M_capacity(__str._M_allocated_capacity);
}
// Must use _M_length() here not _M_set_length() because
// basic_stringbuf relies on writing into unallocated capacity so
// we mess up the contents if we put a '\0' in the string.
_M_length(__str.length());
__str._M_data(__str._M_local_data());
__str._M_set_length(0);
}
移動賦值函數(shù)和移動構(gòu)造函數(shù)類似,就不作過多分析啦。
COW方式下string的實現(xiàn)
先看下部分源代碼了解下COW的basic_string的結(jié)構(gòu):
template <typename _CharT, typename _Traits, typename _Alloc>
class basic_string {
private:
struct _Rep_base {
/**
* string實際數(shù)據(jù)的大小
* 字符串真正存儲的是正常字符串?dāng)?shù)據(jù)加上一個\0,真正的長度其實是_M_length+1
*/
size_type _M_length;
/**
* string當(dāng)前已經(jīng)分配了的內(nèi)存大小
* _M_capacity一定不小于_M_length,內(nèi)存分配總是以_M_capacity+1為單位
*/
size_type _M_capacity;
/**
* _M_refcount表示string的引用計數(shù),取值可以分為三種:
* -1:可能內(nèi)存泄漏,有一個變量指向字符串,字符串可以被更改,不允許拷貝,當(dāng)
* _M_refcount為-1時,表示這個string對象不會再和其它string對象共享啦。
* 0:有一個變量指向字符串,字符串可以被更改。
* n>=1:有n+1個變量指向字符串,對該字符串操作時應(yīng)該加鎖,字符串不可以被更改。
*/
_Atomic_word _M_refcount;
};
/**
* _Rep繼承自_Rep_base
* 主要目的就是繼承_Rep_base的三個成員_M_length、_M_capacity、_M_refcount
*/
struct _Rep : _Rep_base {
// Types:
typedef typename _Alloc::template rebind<char>::other _Raw_bytes_alloc;
static const size_type _S_max_size;
static const _CharT _S_terminal; // \0
static size_type _S_empty_rep_storage[]; // 這里大小不是0,稍后分析
static _Rep& _S_empty_rep() _GLIBCXX_NOEXCEPT {
// NB: Mild hack to avoid strict-aliasing warnings. Note that
// _S_empty_rep_storage is never modified and the punning should
// be reasonably safe in this case.
void* __p = reinterpret_cast<void*>(&_S_empty_rep_storage);
return *reinterpret_cast<_Rep*>(__p);
}
};
// Use empty-base optimization: http://www.cantrip.org/emptyopt.html
struct _Alloc_hider : _Alloc {
_Alloc_hider(_CharT* __dat, const _Alloc& __a) _GLIBCXX_NOEXCEPT : _Alloc(__a), _M_p(__dat) {}
_CharT* _M_p; // The actual data,這里的_M_p指向存儲實際數(shù)據(jù)的對象地址
};
public:
static const size_type npos = static_cast<size_type>(-1); // 0xFFFFFFFF
private:
/**
* _M_dataplus是basic_string內(nèi)部唯一的一個成員變量,
* 內(nèi)部有個_M_p成員,指向存儲實際的數(shù)據(jù)的對象,是_Rep對象的指針
*/
mutable _Alloc_hider _M_dataplus;
};
具體分析可以看代碼中注釋,可以分析出COW的string結(jié)構(gòu)如圖:
前面程序喵分析過深拷貝方式下string的局部內(nèi)存為M_local_buf,那COW下string的S_empty_rep_storage是什么樣子呢?直接看源代碼:
// Linker sets _S_empty_rep_storage to all 0s (one reference, empty string)
// at static init time (before static ctors are run).
template <typename _CharT, typename _Traits, typename _Alloc>
typename basic_string<_CharT, _Traits, _Alloc>::size_type basic_string<_CharT, _Traits, _Alloc>::_Rep::
_S_empty_rep_storage[(sizeof(_Rep_base) + sizeof(_CharT) + sizeof(size_type) - 1) / sizeof(size_type)];
再分析下構(gòu)造函數(shù):
/**
* 使_M_dataplus指向_S_construct函數(shù)返回的內(nèi)存
*/
template <typename _CharT, typename _Traits, typename _Alloc>
basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT* __s, size_type __n, const _Alloc& __a)
: _M_dataplus(_S_construct(__s, __s + __n, __a), __a) {}
/**
* 返回一段內(nèi)存,這段內(nèi)存可以是本地空字符的內(nèi)存,也可以是內(nèi)存分配單元分配的內(nèi)存
*/
template <typename _CharT, typename _Traits, typename _Alloc>
template <typename _InIterator>
_CharT* basic_string<_CharT, _Traits, _Alloc>::_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;
}
/**
* len < 128字節(jié)時,分配len字節(jié)
* 否則,以128為單位,每次擴容2倍大小
* 稍后相信分析
*/
_Rep* __r = _Rep::_S_create(__len, size_type(0), __a);
/**
* 將__buf指向的內(nèi)存拷貝到數(shù)據(jù)真實存放的地址,_M_refdata()指向數(shù)據(jù)真實存放的地址
* _M_refdata()函數(shù)實現(xiàn)如下,可以通過上面畫的string結(jié)構(gòu)圖分析:
* _CharT* _M_refdata() throw() { return reinterpret_cast<_CharT*>(this + 1); }
* this+1就是數(shù)據(jù)真正的地址,這里的1代表sizeof(_Rep)
*/
_M_copy(__r->_M_refdata(), __buf, __len);
__try {
/**
* 這里的擴容機制和上面介紹的相同,這里就不過多介紹
*/
while (__beg != __end) {
if (__len == __r->_M_capacity) {
// Allocate more space.
_Rep* __another = _Rep::_S_create(__len + 1, __len, __a);
_M_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;
}
/**
* 設(shè)置string的長度,同時設(shè)置該string是可共享的,稍后分析
*/
__r->_M_set_length_and_sharable(__len);
return __r->_M_refdata();
}
再看下string內(nèi)部_M_create是如何申請內(nèi)存的
template <typename _CharT, typename _Traits, typename _Alloc>
typename basic_string<_CharT, _Traits, _Alloc>::_Rep* basic_string<_CharT, _Traits, _Alloc>::_Rep::_S_create(
size_type __capacity, size_type __old_capacity, const _Alloc& __alloc) {
if (__capacity > _S_max_size) __throw_length_error(__N("basic_string::_S_create"));
/**
* __pagesize是頁的大小,每次內(nèi)存分配的最小單位
* __malloc_header_zize是malloc分配內(nèi)存額外需要的空間,存儲內(nèi)存實際的長度信息
*/
const size_type __pagesize = 4096;
const size_type __malloc_header_size = 4 * sizeof(void*);
/**
* 每次兩倍擴容
*/
if (__capacity > __old_capacity && __capacity < 2 * __old_capacity) __capacity = 2 * __old_capacity;
/**
* 看了前面的結(jié)構(gòu)圖您應(yīng)該就能明白為什么是這么計算,這里的+1是存儲字符串的結(jié)束符
*/
size_type __size = (__capacity + 1) * sizeof(_CharT) + sizeof(_Rep);
/**
* 因為內(nèi)存是以頁為基本單位分配的,所以這里做了一些優(yōu)化,保證分配內(nèi)存的大小是內(nèi)存頁的整數(shù)倍
*/
const size_type __adj_size = __size + __malloc_header_size;
if (__adj_size > __pagesize && __capacity > __old_capacity) {
const size_type __extra = __pagesize - __adj_size % __pagesize;
__capacity += __extra / sizeof(_CharT);
// Never allocate a string bigger than _S_max_size.
if (__capacity > _S_max_size) __capacity = _S_max_size;
__size = (__capacity + 1) * sizeof(_CharT) + sizeof(_Rep);
}
// NB: Might throw, but no worries about a leak, mate: _Rep()
// does not throw.
void* __place = _Raw_bytes_alloc(__alloc).allocate(__size);
/**
* 這里是placement new,表示在__place內(nèi)存位置處調(diào)用_Rep構(gòu)造函數(shù)
*/
_Rep* __p = new (__place) _Rep;
__p->_M_capacity = __capacity;
/**
* 設(shè)置其可共享,實現(xiàn)如下
* void _M_set_sharable() _GLIBCXX_NOEXCEPT { this->_M_refcount = 0; }
*/
__p->_M_set_sharable();
return __p;
}
這里有關(guān)于malloc的知識點可以看我之前寫的文章:xxx前面Rep有個_M_set_length_and_sharable方法,看下它的源碼:
/**
* 如果當(dāng)前內(nèi)存指向地址是本地內(nèi)存則什么都不做,否則
* 設(shè)置長度為n
* 設(shè)置其可共享,其實就是設(shè)置引用計數(shù)為0
* 同時在最后添加一個結(jié)束符\0
*/
void _M_set_length_and_sharable(size_type __n) _GLIBCXX_NOEXCEPT {
if (__builtin_expect(this != &_S_empty_rep(), false))
{
this->_M_set_sharable(); // One reference.
this->_M_length = __n;
traits_type::assign(this->_M_refdata()[__n], _S_terminal);
}
}
void _M_set_sharable() _GLIBCXX_NOEXCEPT { this->_M_refcount = 0; }
COW版本主要就是為了避免過多的拷貝,這里看下string的拷貝構(gòu)造函數(shù):
/**
* 這里是string的構(gòu)造函數(shù),主要是調(diào)用_Rep的_M_grab函數(shù)
*/
basic_string(const basic_string& __str, const _Alloc& __a)
: _M_dataplus(__str._M_rep()->_M_grab(__a, __str.get_allocator()), __a) {}
/**
* 前面已經(jīng)介紹過為什么+1,這里您應(yīng)該就知道為什么-1啦
*/
_Rep* _M_rep() const _GLIBCXX_NOEXCEPT { return &((reinterpret_cast<_Rep*>(_M_data()))[-1]); }
/**
* _M_grab函數(shù)決定是將引用計數(shù)+1還是拷貝一份
* 如果_M_is_leaked()表示不可以共享,則需要拷貝一份
*/
_CharT* _M_grab(const _Alloc& __alloc1, const _Alloc& __alloc2) {
return (!_M_is_leaked() && __alloc1 == __alloc2) ? _M_refcopy() : _M_clone(__alloc1);
}
/**
* 如果引用計數(shù)小于0,則為true,前面有過約定
*/
bool _M_is_leaked() const _GLIBCXX_NOEXCEPT {
// _M_refcount is mutated concurrently by _M_refcopy/_M_dispose,
// so we need to use an atomic load. However, _M_is_leaked
// predicate does not change concurrently (i.e. the string is either
// leaked or not), so a relaxed load is enough.
return __atomic_load_n(&this->_M_refcount, __ATOMIC_RELAXED) < 0;
return this->_M_refcount < 0;
}
/**
* 引用拷貝,其實就是引用計數(shù)+1
*/
_CharT* _M_refcopy() throw() {
#if _GLIBCXX_FULLY_DYNAMIC_STRING == 0
if (__builtin_expect(this != &_S_empty_rep(), false))
#endif
__gnu_cxx::__atomic_add_dispatch(&this->_M_refcount, 1);
return _M_refdata();
} // XXX MT
/**
* 深拷貝
*/
template <typename _CharT, typename _Traits, typename _Alloc>
_CharT* basic_string<_CharT, _Traits, _Alloc>::_Rep::_M_clone(const _Alloc& __alloc, size_type __res) {
// Requested capacity of the clone.
const size_type __requested_cap = this->_M_length + __res;
_Rep* __r = _Rep::_S_create(__requested_cap, this->_M_capacity, __alloc);
if (this->_M_length) _M_copy(__r->_M_refdata(), _M_refdata(), this->_M_length);
__r->_M_set_length_and_sharable(this->_M_length);
return __r->_M_refdata();
}
再分析下string的析構(gòu)函數(shù):
/**
* string的析構(gòu)函數(shù),調(diào)用了_M_dispose函數(shù)
*/
~basic_string() _GLIBCXX_NOEXCEPT { _M_rep()->_M_dispose(this->get_allocator()); }
/**
* 將引用計數(shù)-1,如果引用計數(shù) <= 0,則釋放內(nèi)存
*/
void _M_dispose(const _Alloc& __a) _GLIBCXX_NOEXCEPT {
if (__builtin_expect(this != &_S_empty_rep(), false))
{
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&this->_M_refcount);
if (__gnu_cxx::__exchange_and_add_dispatch(&this->_M_refcount, -1) <= 0) {
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&this->_M_refcount);
_M_destroy(__a);
}
}
} // XXX MT
template <typename _CharT, typename _Traits, typename _Alloc>
void basic_string<_CharT, _Traits, _Alloc>::_Rep::_M_destroy(const _Alloc& __a) throw() {
const size_type __size = sizeof(_Rep_base) + (this->_M_capacity + 1) * sizeof(_CharT);
_Raw_bytes_alloc(__a).deallocate(reinterpret_cast<char*>(this), __size);
}
data()和c_str()的區(qū)別
我們以前學(xué)習(xí)工作過程中都知道str有data和c_str函數(shù),看資料都說它們的區(qū)別是一個帶\0結(jié)束符,一個不帶。這里看下源碼:
const _CharT* c_str() const _GLIBCXX_NOEXCEPT { return _M_data(); }
const _CharT* data() const _GLIBCXX_NOEXCEPT { return _M_data(); }
這里可以看見它倆沒有任何區(qū)別,因為\0結(jié)束符其實在最開始構(gòu)造string對象的時候就已經(jīng)添加啦。
to_string是怎么實現(xiàn)的?
這里直接看代碼:
inline string to_string(int __val) {
return __gnu_cxx::__to_xstring<string>(&std::vsnprintf, 4 * sizeof(int), "%d", __val);
}
inline string to_string(unsigned __val) {
return __gnu_cxx::__to_xstring<string>(&std::vsnprintf, 4 * sizeof(unsigned), "%u", __val);
}
inline string to_string(long __val) {
return __gnu_cxx::__to_xstring<string>(&std::vsnprintf, 4 * sizeof(long), "%ld", __val);
}
template <typename _String, typename _CharT = typename _String::value_type>
_String __to_xstring(int (*__convf)(_CharT*, std::size_t, const _CharT*, __builtin_va_list), std::size_t __n,
const _CharT* __fmt, ...) {
// XXX Eventually the result should be constructed in-place in
// the __cxx11 string, likely with the help of internal hooks.
_CharT* __s = static_cast<_CharT*>(__builtin_alloca(sizeof(_CharT) * __n));
__builtin_va_list __args;
__builtin_va_start(__args, __fmt);
const int __len = __convf(__s, __n, __fmt, __args);
__builtin_va_end(__args);
return _String(__s, __s + __len);
}
這里可以看出所有的數(shù)值類型轉(zhuǎn)string,都是通過vsnprintf來實現(xiàn),具體vsnprintf是什么這里就不過多介紹啦,讀者可以自行查找下相關(guān)用法哈。
關(guān)于std::string的分析就到這里,前面為了讓您看源碼看的更清晰,程序喵對代碼添加了詳細(xì)的注釋,同時做了適當(dāng)?shù)膭h減,但一定是正確的源代碼,大家可放心閱讀。相信您看完上面的源碼分析可以回答出文章開頭那幾個問題并有所收獲。
參考資料
gcc-9.2.0源代碼 <string> 文件
gcc-9.2.0源代碼<basic_string.h>文件
gcc-9.2.0源代碼<basic_string.tcc>文件
https://blog.csdn.net/sdoyuxuan/article/details/76230520
https://blog.csdn.net/ybxuwei/article/details/51326830
https://my.oschina.net/u/3882675/blog/1925372
往期推薦
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!