最近踩坑发现QString实现和std::string实现机制略有不同,了解其内存模型对于使用QString和std::string和后续的bugfix都有很大的帮助,现记录分享如下。
QString
是Qt框架中的一个字符串类,它提供了一种高效、可扩展的字符串处理方法。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实际会将持有的字符串保存在其私有成员变量中
typedef QStringData Data;
private:
Data *d;
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
拷贝构造函数如下
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
是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
会自动切换到动态内存分配。
内存管理器:std::string通常使用内存管理器(如std::allocator
)来分配和释放内存。内存管理器可以根据实际需求和内存使用模式来优化内存分配策略。例如,内存管理器可以使用内存池技术来重用已分配的内存块,从而减少内存分配和释放的开销。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)
_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 删除。