前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >QString和Std::String

QString和Std::String

原创
作者头像
lealc
修改2024-06-12 20:30:23
1930
修改2024-06-12 20:30:23

前言

最近踩坑发现QString实现和std::string实现机制略有不同,了解其内存模型对于使用QString和std::string和后续的bugfix都有很大的帮助,现记录分享如下。

  • qt基于qt 5.15.2版本
  • std::string则基于C++20

QString

QString是Qt框架中的一个字符串类,它提供了一种高效、可扩展的字符串处理方法。QString的内存模型主要基于以下几个方面:

  • 隐式共享(Implicit Sharing):QString使用隐式共享来实现内存管理。这意味着当你创建一个QString对象的副本时,实际上并不会复制原始字符串的内容。相反,新的QString对象会共享原始对象的内存。这种方法可以显著减少内存使用和提高性能,特别是在处理大量字符串时。当你对其中一个QString对象进行修改时,Qt会自动创建一个新的内存块来存储修改后的字符串,而原始字符串的内存仍然保持不变。
  • 字符编码:QString支持多种字符编码,如UTF-8、UTF-16和UTF-32。这使得QString能够处理各种语言和字符集。在内部,QString使用UTF-16编码来存储字符串。这种编码方式允许QString在处理大多数字符时保持高效,同时也支持包括表情符号在内的Unicode字符。
  • 内存分配:QString使用QByteArray作为其内部存储。QByteArray是一个可变大小的字节数组,它使用预分配策略来优化内存分配。当字符串增长时,QByteArray会预分配额外的内存,以减少内存重新分配的次数。这种策略有助于提高字符串操作的性能。
  • 字符串操作:QString提供了丰富的字符串操作方法,如拼接、截取、查找、替换等。这些操作通常都是高效的,因为它们利用了QString的内部表示和内存管理策略。在执行字符串操作时,QString会尽量避免不必要的内存分配和复制,从而提高性能。

总之,QString的内存模型主要基于隐式共享、字符编码、内存分配和字符串操作等方面。这些设计使得QString在处理字符串时具有高效、可扩展的性能。在使用QString时,请确保遵循Qt框架的最佳实践和建议,以充分利用其内存模型和性能优势。

QString实际会将持有的字符串保存在其私有成员变量中

代码语言:C++
复制
typedef QStringData Data;

private:
  Data *d;
代码语言:C++
复制
inline QString::QString() noexcept : d(Data::sharedNull()) {}
inline QString::~QString() { if (!d->ref.deref()) Data::deallocate(d); }

inline bool deref() noexcept {
        int count = atomic.loadRelaxed();
#if !defined(QT_NO_UNSHARABLE_CONTAINERS)
        if (count == 0) // !isSharable
            return false;
#endif
        if (count == -1) // isStatic
            return true;
        return atomic.deref();
    }

析构函数实际上会将引用计数减一,如果引用计数减到0才会做实质性的销毁操作:deallocate

拷贝构造函数如下

代码语言:C++
复制
inline QString::QString(const QString &other) noexcept : d(other.d)
{ Q_ASSERT(&other != this); d->ref.ref(); }

inline bool ref() noexcept {
        int count = atomic.loadRelaxed();
#if !defined(QT_NO_UNSHARABLE_CONTAINERS)
        if (count == 0) // !isSharable
            return false;
#endif
        if (count != -1) // !isStatic
            atomic.ref();
        return true;
    }

实际上,拷贝构造函数也是拷贝其中实际数据Data,然后将引用计数+1。

Std::String

std::string是C++标准库中的一个字符串类,它提供了一种高效、可扩展的字符串处理方法。

std::string的内存模型主要基于以下几个方面:

  • 动态内存分配:std::string使用动态内存分配来存储字符串的内容。这意味着当字符串增长时,std::string会自动分配更多的内存来容纳新的字符。这种方法允许std::string在运行时根据需要调整其大小,同时也避免了使用固定大小的字符数组可能导致的内存浪费或溢出问题。
  • 内存分配策略:std::string使用一种称为“短字符串优化”(Short String Optimization, SSO)的技术来优化小字符串的内存分配。对于较短的字符串(通常小于16个字符),std::string会在栈上分配足够的空间来存储字符串,而不是使用动态内存分配。这种优化可以减少内存分配和释放的开销,提高性能。
  • 字符编码:std::string通常使用字符编码(如ASCII或UTF-8)来存储字符串。这使得std::string能够处理各种语言和字符集。在内部,std::string使用字节(char类型)来表示字符。这种编码方式允许std::string在处理大多数字符时保持高效,同时也支持包括表情符号在内的Unicode字符。
  • 字符串操作:std::string提供了丰富的字符串操作方法,如拼接、截取、查找、替换等。这些操作通常都是高效的,因为它们利用了std::string的内部表示和内存管理策略。在执行字符串操作时,std::string会尽量避免不必要的内存分配和复制,从而提高性能。

总之,std::string的内存模型主要基于动态内存分配、内存分配策略、字符编码和字符串操作等方面。这些设计使得std::string在处理字符串时具有高效、可扩展的性能。在使用std::string时,请确保遵循C++标准库的最佳实践和建议,以充分利用其内存模型和性能优势。

std::string在执行字符串操作时,通过一些内部优化策略来尽量避免不必要的内存分配和复制,从而提高性能。以下是一些关键的优化策略:

  • 预分配策略:在执行字符串操作(如拼接、替换等)时,std::string会预先分配足够的内存来容纳操作后的字符串。这样,在执行操作时,就不需要频繁地重新分配内存。预分配策略可以减少内存分配和释放的开销,提高性能。
  • 短字符串优化(SSO):对于较短的字符串(通常小于16个字符),std::string会在栈上分配足够的空间来存储字符串,而不是使用动态内存分配。这种优化可以减少内存分配和释放的开销,提高性能。当字符串长度增长到超过SSO阈值时,std::string会自动切换到动态内存分配。 内存管理器:std::string通常使用内存管理器(如std::allocator)来分配和释放内存。内存管理器可以根据实际需求和内存使用模式来优化内存分配策略。例如,内存管理器可以使用内存池技术来重用已分配的内存块,从而减少内存分配和释放的开销。
  • 引用计数和Copy-On-Write(COW):在某些实现中,std::string可能使用引用计数和Copy-On-Write策略来优化字符串复制操作。当你创建一个std::string对象的副本时,实际上并不会复制原始字符串的内容。相反,新的std::string对象会共享原始对象的内存,并增加原始对象的引用计数。当你对其中一个std::string对象进行修改时,std::string会自动创建一个新的内存块来存储修改后的字符串,而原始字符串的内存仍然保持不变。这种方法可以显著减少内存使用和提高性能,特别是在处理大量字符串时。
  • 延迟内存分配:在某些情况下,std::string可能会延迟内存分配,直到实际需要时才进行分配。例如,当你创建一个空的std::string对象时,它可能不会立即分配内存。相反,它会等到你尝试向其中添加字符时才进行内存分配。这种策略可以减少不必要的内存分配和释放,提高性能。

通过这些优化策略,std::string在执行字符串操作时能够尽量避免不必要的内存分配和复制,从而提高性能。在使用std::string时,请确保遵循C++标准库的最佳实践和建议,以充分利用其内存模型和性能优势。在处理字符串操作时,请确保遵循项目的最佳实践和建议。这将有助于确保代码的正确性、可读性和可维护性。

std::string中的Copy-On-Write(COW)策略在C++11标准中被废弃,原因如下:

线程安全:COW策略在多线程环境下可能导致数据竞争和不一致。当多个线程同时访问和修改共享的std::string对象时,COW策略可能导致未定义的行为。为了解决这个问题,C++11标准要求std::string实现必须是线程安全的,这意味着它们不能再使用COW策略。

性能:COW策略在某些情况下可能导致性能下降。例如,当频繁地创建和修改std::string对象时,COW策略可能导致额外的内存分配和复制开销。为了提高性能,C++11标准要求std::string实现不再使用COW策略。

可移植性:COW策略在不同的实现和平台上可能表现不一致。这可能导致在某些环境下出现问题,从而降低了std::string的可移植性。为了提高可移植性,C++11标准要求std::string实现不再使用COW策略。

因此,在C++11及更高版本的标准中,std::string实现不再使用COW策略。这有助于确保std::string在多线程环境下的线程安全,提高性能和可移植性。在使用std::string时,请确保遵循C++标准库的最佳实践和建议,以充分利用其内存模型和性能优势。在处理字符串操作时,请确保遵循项目的最佳实践和建议。这将有助于确保代码的正确性、可读性和可维护性。

拷贝构造函数如下:注意SSO

(基于C++ 20)

代码语言:C++
复制
_CONSTEXPR20_CONTAINER basic_string(const basic_string& _Right)
        : _Mypair(_One_then_variadic_args_t{}, 
_Alty_traits::select_on_container_copy_construction(_Right._Getal())) {
        auto&& _Alproxy = _GET_PROXY_ALLOCATOR(_Alty, _Getal());
        _Container_proxy_ptr<_Alty> _Proxy(_Alproxy, _Mypair._Myval2);
        _Construct_lv_contents(_Right);
        _Proxy._Release();
    }

_CONSTEXPR20_CONTAINER void _Construct_lv_contents(const basic_string& _Right) {
        // assign by copying data stored in _Right
        // pre: this != &_Right
        // pre: *this owns no memory, iterators orphaned (note:
        // _Buf/_Ptr/_Mysize/_Myres may be garbage init)
        auto& _Right_data             = _Right._Mypair._Myval2;
        const size_type _Right_size   = _Right_data._Mysize;
        const _Elem* const _Right_ptr = _Right_data._Myptr();
        auto& _My_data                = _Mypair._Myval2;

#ifdef __cpp_lib_constexpr_string
        const bool _Stay_small = _Right_size < _BUF_SIZE && !_STD 
is_constant_evaluated();
#else // ^^^ __cpp_lib_constexpr_string / !__cpp_lib_constexpr_string vvv
        const bool _Stay_small = _Right_size < _BUF_SIZE;
#endif // __cpp_lib_constexpr_string

        // NOTE: even if _Right is in large mode, we only go into large mode ourselves 
if the actual size of _Right
        // requires it
        if (_Stay_small) { // stay small, don't allocate
            _Traits::copy(_My_data._Bx._Buf, _Right_ptr, _BUF_SIZE);
            _My_data._Mysize = _Right_size;
            _My_data._Myres  = _BUF_SIZE - 1;
            return;
        }

        auto& _Al                     = _Getal();
        const size_type _New_capacity = (_STD min)(_Right_size | _ALLOC_MASK, 
max_size());
        const pointer _New_array      = _Al.allocate(_New_capacity + 1); // throws
        _Construct_in_place(_My_data._Bx._Ptr, _New_array);

#ifdef __cpp_lib_constexpr_string
        if (_STD is_constant_evaluated()) { // Begin the lifetimes of the objects 
before copying to avoid UB
            _Traits::assign(_Unfancy(_New_array), _New_capacity + 1, _Elem());
        }
#endif // __cpp_lib_constexpr_string
        _Traits::copy(_Unfancy(_New_array), _Right_ptr, _Right_size + 1);
        _My_data._Mysize = _Right_size;
        _My_data._Myres  = _New_capacity;
    }


template <class _Ty, class... _Types>
_CONSTEXPR20_DYNALLOC void _Construct_in_place(_Ty& _Obj, _Types&&... _Args) noexcept(
    is_nothrow_constructible_v<_Ty, _Types...>) {
#ifdef __cpp_lib_constexpr_dynamic_alloc
    if (_STD is_constant_evaluated()) {
        _STD construct_at(_STD addressof(_Obj), _STD forward<_Types>(_Args)...);
    } else
#endif // __cpp_lib_constexpr_dynamic_alloc
    {
        ::new (_Voidify_iter(_STD addressof(_Obj))) _Ty(_STD 
forward<_Types>(_Args)...);
    }
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • QString
  • Std::String
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档