From c2d2757bccc68e1b981df059786c2e76f2969530 Mon Sep 17 00:00:00 2001 From: Giuseppe D'Angelo Date: Sun, 19 May 2019 14:35:52 +0200 Subject: QString/QByteArray: detach immediately in operator[] Unlike any other implicitly shared container, QString/QByteArray have a "lazy detach" mechanism: their operator[] returns a special object; assignment into that object will actually detach. In other words: QString a("Hello"); QCharRef c = a[0]; // does not detach c = 'J'; // detach happens here This allows this behavior: QString a("Hello"); QCharRef c = a[0]; QString b = a; c = 'J'; // detach happens here assert(a == "Jello"); assert(b == "Hello"); Note that this happens only with operator[] -- the mutating iterator APIs instead detach immediately, making the above code have visible side effects in b (at the end, b == "Jello"). The reasons for this special behavior seems to have been lost in the dawn of time: this is something present all the way back since Qt 2, maybe even Qt 1. Holding on to a "reference" while taking copies of a container is documented [1] to be a bad idea, so we shouldn't double check that the users don't do it. This patch: 1) adds an immediate detach in operator[], just like all other containers; 2) adds a warning in debug builds in case QByteRef/QCharRef is going to cause a detach; 3) marks operator[] as [[nodiscard]] to warn users not using Clazy about the (unintended) detach now happening in their code. This paves the way for removal of QCharRef/QByteRef, likely in Qt 7. [1] https://doc.qt.io/qt-5/containers.html#implicit-sharing-iterator-problem [ChangeLog][QtCore][QString] QString::operator[] detaches immediately. Previously, the detach was delayed until a modification was made to the string through the returned QCharRef. [ChangeLog][QtCore][QByteArray] QByteArray::operator[] detaches immediately. Previously, the detach was delayed until a modification was made to the byte array through the returned QByteRef. Change-Id: I9f77ae36759d80dc3202426a798f5b1e5fb2c2c5 Reviewed-by: Thiago Macieira --- src/corelib/tools/qstring.h | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'src/corelib/tools/qstring.h') diff --git a/src/corelib/tools/qstring.h b/src/corelib/tools/qstring.h index 5bc3a87832..0f7b015bef 100644 --- a/src/corelib/tools/qstring.h +++ b/src/corelib/tools/qstring.h @@ -271,9 +271,9 @@ public: inline const QChar at(int i) const; const QChar operator[](int i) const; - QCharRef operator[](int i); + Q_REQUIRED_RESULT QCharRef operator[](int i); const QChar operator[](uint i) const; - QCharRef operator[](uint i); + Q_REQUIRED_RESULT QCharRef operator[](uint i); Q_REQUIRED_RESULT inline QChar front() const { return at(0); } Q_REQUIRED_RESULT inline QCharRef front(); @@ -1089,7 +1089,7 @@ public: if (Q_LIKELY(i < s.d->size)) return s.d->data()[i]; #ifdef QT_DEBUG - warn(EmittingClass::QCharRef); + warn(WarningType::OutOfRange, EmittingClass::QCharRef); #endif return 0; } @@ -1098,10 +1098,14 @@ public: using namespace QtPrivate::DeprecatedRefClassBehavior; if (Q_UNLIKELY(i >= s.d->size)) { #ifdef QT_DEBUG - warn(EmittingClass::QCharRef); + warn(WarningType::OutOfRange, EmittingClass::QCharRef); #endif s.resize(i + 1, QLatin1Char(' ')); } else { +#ifdef QT_DEBUG + if (Q_UNLIKELY(!s.isDetached())) + warn(WarningType::DelayedDetach, EmittingClass::QCharRef); +#endif s.detach(); } s.d->data()[i] = c.unicode(); @@ -1215,9 +1219,9 @@ inline void QString::squeeze() inline QString &QString::setUtf16(const ushort *autf16, int asize) { return setUnicode(reinterpret_cast(autf16), asize); } inline QCharRef QString::operator[](int i) -{ Q_ASSERT(i >= 0); return QCharRef(*this, i); } +{ Q_ASSERT(i >= 0); detach(); return QCharRef(*this, i); } inline QCharRef QString::operator[](uint i) -{ return QCharRef(*this, i); } +{ detach(); return QCharRef(*this, i); } inline QCharRef QString::front() { return operator[](0); } inline QCharRef QString::back() { return operator[](size() - 1); } inline QString::iterator QString::begin() -- cgit v1.2.3