std::string的Copy-On-Write(写时拷贝)技术原理及其简单实现
以下是基于SGI版本
1、std::string
std::string 其实是模板类std::basic_string的实例化,可以在头文件stringfwd.h中查看
std::basic_string的实现可以在basic_string.h和basic_string.tcc文件中查看
2、Copy-On-Write
basic_string有一个写时拷贝的技术,这样可以极大的优化性能,它通过引用计数实现的,
basic_string类的大致构造如上图所示,对于_Rep对象的构建,是先申请堆空间,空间大小是sizeof(_Rep)+字符串capacity长度, 在申请内存的首地址就地new出 _Rep对象,所以basic_string的_M_p指向的实际内存如下图所示。
例如当string A=string B时,A并没有为_M_p重新申请数据内存,而是A. _M_p = B. _M_p, _M_refcount加一,直到A有写入时,才会重新为A. _M_p申请内存,而_M_refcount也会减一,
_Rep中静态的_S_empty_rep_storage成员,是用来存放每一个默认构造的basic_string前提是宏定义_GLIBCXX_FULLY_DYNAMIC_STRING == 0,也就是说其实所有默认构造对象的_M_p都指向同一处内存,这样可以节省很多内存
3、MyString的简单实现
#include <stdio.h> #include <atomic> class MyString { public: struct Rep_base { uint32_t m_length; uint32_t m_capacity; std::atomic<int32_t> m_refcount; }; struct Rep:Rep_base { static uint32_t s_empty_rep_storage[]; // 用于存放所有默认构造的string static const uint32_t s_max_size; // string所允许的最大长度 static const char s_terminal; // 定义结束符 // static Rep& s_empty_rep() { void* p = reinterpret_cast<void*>(&s_empty_rep_storage); return *reinterpret_cast<Rep*>(p); } // 返回string数据的地址 char* refdata() throw() { // this+1的地址就是偏移一个Rep,也就是12个字节后的地址 char* p = reinterpret_cast<char*>(this + 1); return p; } // 堆空间申请新的内存区域,最前端是Req,后面跟着string字符数据 static Rep* create(uint32_t capacity, uint32_t old_capacity) { if (capacity > s_max_size) { return nullptr; } if (capacity > old_capacity && capacity < 2 * old_capacity) { capacity = 2 * old_capacity; } // 这里省略了源码里的页面对齐 //... uint32_t size = (capacity + 1) * sizeof(char) + sizeof(Rep); void* place = malloc(size); Rep * p = new (place) Rep; p->m_capacity = capacity; p->m_refcount.exchange(0); return p; } // 释放内存,销毁对象 void destroy() throw () { free(reinterpret_cast<char*>(this)); } // 设置string长度和引用计数,并追加结束符 // 因此string的size()是不包含结束符的 void set_length_and_sharable(uint32_t n) { if (this != &Rep::s_empty_rep()) { this->m_refcount.exchange(0); this->m_length = n; this->refdata()[n] = s_terminal; } } // 重新创建空间,拷贝原数据 char* clone(uint32_t res = 0) { const uint32_t requested_cap = this->m_capacity + res; Rep* r = Rep::create(requested_cap, this->m_capacity); if (this->m_length) { memcpy( r->refdata(), this->refdata(), this->m_length); } r->set_length_and_sharable(this->m_length); return r->refdata(); } // 引用计数加1 char* refcopy() throw() { if (this != &Rep::s_empty_rep()) { this->m_refcount++; } return refdata(); } // 如果引用计数>=0,则引用计数++,否则重新申请空间拷贝数据 char* grab() { return (m_refcount.load() >= 0) ? refcopy() : clone(); } // 引用计数减一,如果引用计数小于0,销毁内存 void dispose() { if (this != &Rep::s_empty_rep()) { m_refcount--; if (m_refcount.load() < 0) { destroy(); } } } }; struct Alloc_hider { Alloc_hider(char* dat) : m_p(dat) { } char* m_p; // The actual data. }; public: // 默认构造函数 MyString() : m_dataplus(Rep::s_empty_rep().refdata()) { } // 带入参的构造函数 MyString(const char* s) : m_dataplus(m_construct(s, s ? s + strlen(s) : s)) { } // 拷贝构造 MyString(const MyString& s) : m_dataplus(s.m_rep()->grab()) { } // 移植构造,入参需为右值 MyString(MyString&& s) : m_dataplus(s.m_dataplus) { // s对象m_rep()地址交给了this对象,自己指向了全局的初始化空间 s.m_data(Rep::s_empty_rep().refdata()); } MyString& operator=(const MyString& s) { if (m_rep() != s.m_rep()) { char* tmp = s.m_rep()->grab(); // s的引用计数加一 m_rep()->dispose(); // this的引用计数减一 m_data(tmp); } return *this; } ~MyString() // 析构函数 { m_rep()->dispose(); } MyString& operator=(const char* s) { uint32_t n = strlen(s); // m_data()不包含s字符串或者引用计数大于0,则可能需要调整m_data()内存 if (m_disjunct(s) || m_rep()->m_refcount.load() > 0) { return m_replace_safe(0, this->size(), s, n); } else { // s在m_data()内存中 const uint32_t pos = s - m_data(); if (pos >= n) {// m_data()到s的长度足够容纳s字符串,只需要拷贝进去就行 memcpy(m_data(), s, n); } else if (pos) {// 需要内存移动,如果pos为0,则不用做任何操作,置一个结束符就可以 memmove(m_data(), s, n); } m_rep()->set_length_and_sharable(n); // 该函数会置结束符 return *this; } } MyString& operator=(MyString&& s) { // 交换双方指针 char* tmp = s.m_data(); s.m_data(this->m_data()); this->m_data(tmp); return *this; } //MyString& operator=[]() const char* c_str() const { return this->m_data(); } uint32_t size() const { return this->m_rep()->m_length; } uint32_t capacity() const { return this->m_rep()->m_capacity; } private: // 返回Rep对象后的string字符串首地址 char* m_data() const { return m_dataplus.m_p; } // 重新赋值string字符串首地址 char* m_data(char* p) { return (m_dataplus.m_p = p); } // 返回Rep对象地址 Rep* m_rep() const { return &((reinterpret_cast<Rep*> (m_data()))[-1]); } // 构建一个新的对象,返回字符串首地址 char* m_construct(const char* beg, const char* end) { if (beg == end)// 空字符串不处理 { return Rep::s_empty_rep().refdata(); } // 这里先按128字节申请内存 char buf[128]; uint32_t len = 0; while (beg != end && len < sizeof(buf) / sizeof(char)) { buf[len++] = *beg; ++beg; } Rep* r = Rep::create(len, 0); memcpy(r->refdata(), buf, len); while (beg != end) // 如果128字节不够,再重新申请内存,翻倍扩大 { if (len == r->m_capacity) { // 重新翻倍申请内存 Rep* another = Rep::create(len + 1, len); memcpy(another->refdata(), r->refdata(), len); r->destroy(); // 释放内存 r = another; // 重新赋值 } r->refdata()[len++] = *beg; ++beg; } r->set_length_and_sharable(len); return r->refdata(); } // 判断s首地址是否在m_data()字符串地址内,如果在其内, // 则m_data()字符串必然包含s字符串,因为m_data()字符串必然以‘\0’结尾 bool m_disjunct(const char* s) const { return ((s < m_data()) || ((m_data() + m_rep()->m_length) < s)); } // 调整m_data()内存,因为需要把pos位置开始的len1长度替换为len2长度 void m_mutate(uint32_t pos, uint32_t len1, uint32_t len2) { const uint32_t old_size = this->size(); const uint32_t new_size = old_size + len2 - len1; const uint32_t how_much = old_size - pos - len1; // pos+len1后面的内存数据 if (new_size > this->capacity() || m_rep()->m_capacity > 0) { // m_data()长度不够或者引用计数大于0,m_data()就需要重新申请块内存了 Rep* r = Rep::create(new_size, this->capacity()); if (pos) // 把pos前的内存拷贝到新内存中 { memcpy(r->refdata(), m_data(), pos); } if (how_much) // 把pos+len1后面的内存数据后面的数据拷贝到新内存中 { memcpy(r->refdata() + pos + len2, m_data() + pos + len1, how_much); } m_rep()->dispose(); // 原先的rep引用计数减一 m_data(r->refdata()); // m_data()指向新的内存地址 } else if (how_much && len1 != len2) { // 如果len1==len2,则刚好不需要操作m_data() memmove(m_data() + pos + len2, m_data() + pos + len1, how_much); } m_rep()->set_length_and_sharable(new_size); // 新的引用计数置0,设置length并置结束符 } // 将m_data()数据从pos1位置开始的n1长度数据替换为长度为n2的s字符串 MyString& m_replace_safe(uint32_t pos1, uint32_t n1, const char* s, uint32_t n2) { m_mutate(pos1, n1, n2); if (n2) { memcpy(m_data() + pos1, s, n2); } return *this; } private: mutable Alloc_hider m_dataplus; }; uint32_t MyString::Rep::s_empty_rep_storage[ (sizeof(Rep_base) + sizeof(char) + sizeof(uint32_t) - 1) / sizeof(uint32_t)]; const uint32_t MyString::Rep::s_max_size = (((uint32_t(-1) - sizeof(Rep_base)) / sizeof(char)) - 1) / 4; const char MyString::Rep::s_terminal = '\0';