返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++种string的实现方式
  • 134
分享到

C++种string的实现方式

2023-06-16 06:06:18 134人浏览 独家记忆
摘要

这篇文章主要讲解了“c++种string的实现方式”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++种string的实现方式”吧!常见的string实现方式有两种,一种是深拷贝的方式,一种

这篇文章主要讲解了“c++种string的实现方式”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++种string的实现方式”吧!

常见的string实现方式有两种,一种是深拷贝的方式,一种是COW(copy on write)写时拷贝方式,以前多数使用COW方式,但由于目前多线程使用越来越多,COW技术在多线程中会有额外的性能恶化,所以现在多数使用深拷贝的方式,但了解COW的技术实现还是很有必要的。

这里会对这两种方式都进行源码分析,正文内容较少,更多内容都在源码的注释中。

string的内容主要在GCc源码的三个文件中:<string>、<basic_string.h>、<basic_string.tcc>

在分析前先介绍下string或者C++ stl中几个基本的概念:

  •  size: 表示真实数据的大小,一般resize函数改变的就是这个值。

  •  capacity:表示内部实际已经分配的内存大小,capacity一定大于等于size,当size超过这个容量时会触发重新分配机制,一般reserve函数改变的就是这个值。

深拷贝下string的实现

<string>文件中有如下代码:

// file: string  using string = basic_string<char>;

这里可以看到string其实真实的样子是basic_string,这里可以看下basic_string真实的结构:

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) {}                  pointer _M_p;  // The actual data.    };     _Alloc_hider _M_dataplus;          size_type _M_string_length;     enum { _S_local_capacity = 15 / sizeof(_CharT) };          uNIOn {         _CharT _M_local_buf[_S_local_capacity + 1];                  size_type _M_allocated_capacity;    };  };

从这里可以看见整个basic_string的结构如图:

C++种string的实现方式

看下面代码:

string str;

这段代码会调用普通构造函数,对应的源码实现如下:

basic_string() : _M_dataplus(_M_local_data()) { _M_set_length(0); }

而_M_local_data()的实现如下:

const_pointer _M_local_data() const {     return std::pointer_traits<const_pointer>::pointer_to(*_M_local_buf);  }

这里可以看见M_dataplus表示实际存放数据的地方,当string是空的时候,其实就是指向M_local_buf,且_M_string_length是0。

当由char*构造string时,构造函数如下:

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的实现,具体分析可以看下我代码中添加的注释:

  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);    // 现在__capacity是15,注意这个值等会可能会改变    while (__beg != __end && __len < __capacity) {        _M_data()[__len++] = *__beg;        ++__beg;    }        __try {        while (__beg != __end) {            if (__len == __capacity) {                                __capacity = __len + 1;                pointer __another = _M_create(__capacity, __len);                                this->_S_copy(__another, _M_data(), __len);                                _M_dispose();                                _M_data(__another);                                _M_capacity(__capacity);            }            _M_data()[__len++] = *__beg;            ++__beg;        }    }    __catch(...) {                _M_dispose();        __throw_exception_again;    }        _M_set_length(__len);  }

再分析下内部的内存申请函数_M_create:

  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) {        if (__capacity > max_size()) std::__throw_length_error(__N("basic_string::_M_create"));        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();    }        return _Alloc_traits::allocate(_M_get_allocator(), __capacity + 1);  }

再分析下内部的内存释放函数_M_dispose函数:

  void _M_dispose() {    if (!_M_is_local()) _M_destroy(_M_allocated_capacity);  }    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的拷贝构造函数:

  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的赋值构造函数:

  basic_string& operator=(const basic_string& __str) { return this->assign(__str); }    basic_string& assign(const basic_string& __str) {    this->_M_assign(__str);    return *this;  }    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();                if (__rsize > __capacity) {            size_type __new_capacity = __rsize;            pointer __tmp = _M_create(__new_capacity, __capacity);            _M_dispose();            _M_data(__tmp);            _M_capacity(__new_capacity);        }                if (__rsize) this->_S_copy(_M_data(), __str._M_data(), __rsize);        _M_set_length(__rsize);    }  }

再分析下移动构造函数:

  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);  }

移动赋值函数和移动构造函数类似,就不作过多分析啦。

COW方式下string的实现

先看下部分源代码了解下COW的basic_string的结构:

template <typename _CharT, typename _Traits, typename _Alloc>  class basic_string {    private:    struct _Rep_base {                size_type _M_length;                size_type _M_capacity;                _Atomic_Word _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指向存储实际数据的对象地址    };    public:    static const size_type npos = static_cast<size_type>(-1); // 0xFFFFFFFF    private:        mutable _Alloc_hider _M_dataplus;  };

具体分析可以看代码中注释,可以分析出COW的string结构如图:

C++种string的实现方式

前面程序喵分析过深拷贝方式下string的局部内存为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)];

再分析下构造函数:

  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) {}    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 _GLIBCXX_FULLY_DYNAMIC_STRING == 0    if (__beg == __end && __a == _Alloc()) return _S_empty_rep()._M_refdata();  #endif    // Avoid reallocation for common case.    _CharT __buf[128];    size_type __len = 0;    while (__beg != __end && __len < sizeof(__buf) / sizeof(_CharT)) {        __buf[__len++] = *__beg;        ++__beg;    }        _Rep* __r = _Rep::_S_create(__len, size_type(0), __a);        _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;    }        __r->_M_set_length_and_sharable(__len);    return __r->_M_refdata();  }

再看下string内部_M_create是如何申请内存的

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"));        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;        size_type __size = (__capacity + 1) * sizeof(_CharT) + sizeof(_Rep);        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);        _Rep* __p = new (__place) _Rep;    __p->_M_capacity = __capacity;        __p->_M_set_sharable();    return __p;  }  这里有关于malloc的知识点可以看我之前写的文章:xxx前面Rep有个_M_set_length_and_sharable方法,看下它的源码:    void _M_set_length_and_sharable(size_type __n) _GLIBCXX_NOEXCEPT {  #if _GLIBCXX_FULLY_DYNAMIC_STRING == 0      if (__builtin_expect(this != &_S_empty_rep(), false))  #endif      {         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的拷贝构造函数:

  basic_string(const basic_string& __str, const _Alloc& __a)      : _M_dataplus(__str._M_rep()->_M_grab(__a, __str.get_allocator()), __a) {}    _Rep* _M_rep() const _GLIBCXX_NOEXCEPT { return &((reinterpret_cast<_Rep*>(_M_data()))[-1]); }    _CharT* _M_grab(const _Alloc& __alloc1, const _Alloc& __alloc2) {      return (!_M_is_leaked() && __alloc1 == __alloc2) ? _M_refcopy() : _M_clone(__alloc1);  }    bool _M_is_leaked() const _GLIBCXX_NOEXCEPT {  #if defined(__GTHREADS)      // _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;  #else      return this->_M_refcount < 0;  #endif  }    _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的析构函数:    ~basic_string() _GLIBCXX_NOEXCEPT { _M_rep()->_M_dispose(this->get_allocator()); }    void _M_dispose(const _Alloc& __a) _GLIBCXX_NOEXCEPT {  #if _GLIBCXX_FULLY_DYNAMIC_STRING == 0      if (__builtin_expect(this != &_S_empty_rep(), false))  #endif      {          // 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()的区别

我们以前学习工作过程中都知道str有data和c_str函数,看资料都说它们的区别是一个带\0结束符,一个不带。这里看下源码:

const _CharT* c_str() const _GLIBCXX_NOEXCEPT { return _M_data(); }  const _CharT* data() const _GLIBCXX_NOEXCEPT { return _M_data(); }

这里可以看见它俩没有任何区别,因为\0结束符其实在最开始构造string对象的时候就已经添加啦。

to_string是怎么实现的?

这里直接看代码:

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);  }

这里可以看出所有的数值类型转string,都是通过vsnprintf来实现,具体vsnprintf是什么这里就不过多介绍啦,读者可以自行查找下相关用法。

感谢各位的阅读,以上就是“C++种string的实现方式”的内容了,经过本文的学习后,相信大家对C++种string的实现方式这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: C++种string的实现方式

本文链接: https://lsjlt.com/news/282591.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++种string的实现方式
    这篇文章主要讲解了“C++种string的实现方式”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++种string的实现方式”吧!常见的string实现方式有两种,一种是深拷贝的方式,一种...
    99+
    2023-06-16
  • C#格式化JSON的两种实现方式
    目录实现功能:开发环境:实现代码:当我们拿到一大段JSON字符串的时候,分析起来简直头皮发麻,相信很大一部分朋友也都会直接去BEJSON等网站去做一个JSON格式化,已方便自己查看数...
    99+
    2024-04-02
  • C#实现单例模式的多种方式
    什么是单例模式 这里我就不做过多的解释了, 毕竟关于Singleton的资料实在是太多太多了。点击这里 简单的思路就是, 创建对象单例的动作转移到另外的行为上面, 利用一个行为去创建...
    99+
    2024-04-02
  • C#多态的三种实现方式(小结)
    C#实现多态主要有3种方法,虚方法,抽象类,接口 1 虚方法 在父类的方法前面加关键字virtual, 子类重写该方法时在方法名前面加上override关键字,例如下面的Perso...
    99+
    2024-04-02
  • C#实现文本读取的7种方式
    目录前言第一个方式第二个方式第三个方式第四个方式第五个方式第六个方式第七个方式前言 文本读取在上位机开发中经常会使用到,实现的方式也有很多种,今天跟大家分享一下C#实现读取读取的7种...
    99+
    2024-04-02
  • c++继承的实现方式有哪几种
    在C++中,有三种继承的实现方式:公有继承、私有继承和保护继承。 公有继承: 公有继承是最常用的继承方式。使用关键字"publi...
    99+
    2023-10-26
    c++
  • C#实现观察者模式(Observer Pattern)的两种方式
    在观察者模式中有2个要素:一个是被观察对象,另一个是观察者。但被观察对象的状态发生改变会通知观察者。 举例:把订阅报纸的人看作是观察者,把报纸看作被观察对象。每当有新的新闻就要通知订...
    99+
    2024-04-02
  • C# Socket数据接收的三种实现方式
    Stream.Read 方法 当在派生类中重写时,从当前流读取字节序列,并将此流中的位置提升读取的字节数。 语法: public abstract int Read(byt...
    99+
    2024-04-02
  • C/C++实现快速排序算法的两种方式实例
    目录介绍流程如下实现方式一方式二总结介绍 快速排序是对冒泡排序算法的一种改进,快速排序算法通过多次比较和交换来实现排序。 流程如下 (图片来自百度) 实现 以下有两种实现方式,说是...
    99+
    2024-04-02
  • C# 三种方式实现Socket数据接收
    目录Stream.Read 方法将数据接收放到 while (true)Stream.Read 方法 当在派生类中重写时,从当前流读取字节序列,并将此流中的位置提升读取的字节数。 语...
    99+
    2024-04-02
  • C++单例模式的几种实现方法详解
    目录局部静态变量方式静态成员变量指针方式智能指针方式辅助类智能指针单例模式通用的单例模板类总结局部静态变量方式 //通过静态成员变量实现单例 //懒汉式 class Single2 ...
    99+
    2024-04-02
  • C#实现单例模式的6种方法小结
    目录介绍Version 1 - 非线程安全Version 2 - 简单的线程安全Version 3 - Double-check locking的线程安全Version 4 - 不完...
    99+
    2024-04-02
  • C++实现线程同步的四种方式总结
    目录内核态互斥变量 事件对象资源信号量用户态关键代码内核态 互斥变量  互斥对象包含一个使用数量,一个线程ID和一个计数器。其中线程ID用于标识系统中的哪个线程当...
    99+
    2022-11-16
    C++线程同步方式 C++线程同步
  • 三种工厂模式的C++实现
    引出工厂模式的设计问题◆ 1.为了提高内聚(Cohesion)和松耦合(Coupling),我们经常会抽象出一些类的公共接口以形成抽象基类或者接口。这样我们可以通过声明一个指向基类的指针来指向实际的子类实现...
    99+
    2024-04-02
  • C++深浅拷贝及简易string类实现方式
    目录三种拷贝方式浅拷贝深拷贝写时拷贝VS与GCC中的拷贝方式Windows VS2022Linux GCC简易string类传统版写法的string类现代版写法string类总结三种...
    99+
    2023-02-05
    C++深浅拷贝 C++实现string类 C++ string类
  • C++中string字符串分割函数split()的4种实现方法
    目录一、使用stringstream流二、使用string类提供的find方法与substr方法三、使用C库函数strtok四、使用regex_token_iterator(正则表达...
    99+
    2024-04-02
  • C# 实现Eval(字符串表达式)的三种方法
    目录一、背景二、代码三、测试一、背景 假如给定一个字符串表达式"-12 * ( - 2.2 + 7.7 ) - 44 * 2",让你计算结果,熟悉JavaScript的都知道有个E...
    99+
    2024-04-02
  • Struts2 实现Action的几种方式
    Struts2 实现 Action 的几种方式有以下几种:1. 实现 Action 接口:可以实现 Struts2 提供的 Action 接口,该接口定义了执行 Action 的方法 execute(),通过该方法可以处理请求并返回结果...
    99+
    2023-08-11
    Struts2
  • Struts2实现Action的几种方式
    Struts2实现Action的几种方式有以下几种:1. 实现Action接口:创建一个类并实现com.opensymphony.x...
    99+
    2023-08-17
    Struts2
  • C/C++实现快速排序(两种方式)图文详解
    目录介绍实现方式一方式二总结介绍 快速排序是对冒泡排序算法的一种改进,快速排序算法通过多次比较和交换来实现排序。 流程如下:   实现 以下有两种实现方式,说是两种,其...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作