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';
posted @ 2021-01-14 11:13  ho966  阅读(820)  评论(0编辑  收藏  举报