diff --git a/src/corelib/tools/qhash.cpp b/src/corelib/tools/qhash.cpp index ca5b7ffd55..f048aa01de 100644 --- a/src/corelib/tools/qhash.cpp +++ b/src/corelib/tools/qhash.cpp @@ -405,328 +405,6 @@ uint qt_hash(QStringView key, uint chained) noexcept return h; } -/* - The prime_deltas array contains the difference between a power - of two and the next prime number: - - prime_deltas[i] = nextprime(2^i) - 2^i - - Basically, it's sequence A092131 from OEIS, assuming: - - nextprime(1) = 1 - - nextprime(2) = 2 - and - - left-extending it for the offset 0 (A092131 starts at i=1) - - stopping the sequence at i = 28 (the table is big enough...) -*/ - -static const uchar prime_deltas[] = { - 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 17, 27, 3, - 1, 29, 3, 21, 7, 17, 15, 9, 43, 35, 15, 0, 0, 0, 0, 0 -}; - -/* - The primeForNumBits() function returns the prime associated to a - power of two. For example, primeForNumBits(8) returns 257. -*/ - -static inline int primeForNumBits(int numBits) -{ - return (1 << numBits) + prime_deltas[numBits]; -} - -/* - Returns the smallest integer n such that - primeForNumBits(n) >= hint. -*/ -static int countBits(int hint) -{ - int numBits = 0; - int bits = hint; - - while (bits > 1) { - bits >>= 1; - numBits++; - } - - if (numBits >= (int)sizeof(prime_deltas)) { - numBits = sizeof(prime_deltas) - 1; - } else if (primeForNumBits(numBits) < hint) { - ++numBits; - } - return numBits; -} - -/* - A QHash has initially around pow(2, MinNumBits) buckets. For - example, if MinNumBits is 4, it has 17 buckets. -*/ -const int MinNumBits = 4; - -const QHashData QHashData::shared_null = { - nullptr, nullptr, Q_REFCOUNT_INITIALIZE_STATIC, 0, 0, MinNumBits, 0, 0, 0, true, false, 0 -}; - -void *QHashData::allocateNode(int nodeAlign) -{ - void *ptr = strictAlignment ? qMallocAligned(nodeSize, nodeAlign) : malloc(nodeSize); - Q_CHECK_PTR(ptr); - return ptr; -} - -void QHashData::freeNode(void *node) -{ - if (strictAlignment) - qFreeAligned(node); - else - free(node); -} - -QHashData *QHashData::detach_helper(void (*node_duplicate)(Node *, void *), - void (*node_delete)(Node *), - int nodeSize, - int nodeAlign) -{ - union { - QHashData *d; - Node *e; - }; - if (this == &shared_null) - qt_initialize_qhash_seed(); // may throw - d = new QHashData; - d->fakeNext = nullptr; - d->buckets = nullptr; - d->ref.initializeOwned(); - d->size = size; - d->nodeSize = nodeSize; - d->userNumBits = userNumBits; - d->numBits = numBits; - d->numBuckets = numBuckets; - d->seed = (this == &shared_null) ? uint(qt_qhash_seed.loadRelaxed()) : seed; - d->sharable = true; - d->strictAlignment = nodeAlign > 8; - d->reserved = 0; - - if (numBuckets) { - QT_TRY { - d->buckets = new Node *[numBuckets]; - } QT_CATCH(...) { - // restore a consistent state for d - d->numBuckets = 0; - // roll back - d->free_helper(node_delete); - QT_RETHROW; - } - - Node *this_e = reinterpret_cast(this); - for (int i = 0; i < numBuckets; ++i) { - Node **nextNode = &d->buckets[i]; - Node *oldNode = buckets[i]; - while (oldNode != this_e) { - QT_TRY { - Node *dup = static_cast(allocateNode(nodeAlign)); - - QT_TRY { - node_duplicate(oldNode, dup); - } QT_CATCH(...) { - freeNode( dup ); - QT_RETHROW; - } - - *nextNode = dup; - nextNode = &dup->next; - oldNode = oldNode->next; - } QT_CATCH(...) { - // restore a consistent state for d - *nextNode = e; - d->numBuckets = i+1; - // roll back - d->free_helper(node_delete); - QT_RETHROW; - } - } - *nextNode = e; - } - } - return d; -} - -void QHashData::free_helper(void (*node_delete)(Node *)) -{ - if (node_delete) { - Node *this_e = reinterpret_cast(this); - Node **bucket = reinterpret_cast(this->buckets); - - int n = numBuckets; - while (n--) { - Node *cur = *bucket++; - while (cur != this_e) { - Node *next = cur->next; - node_delete(cur); - freeNode(cur); - cur = next; - } - } - } - delete [] buckets; - delete this; -} - -QHashData::Node *QHashData::nextNode(Node *node) -{ - union { - Node *next; - Node *e; - QHashData *d; - }; - next = node->next; - Q_ASSERT_X(next, "QHash", "Iterating beyond end()"); - if (next->next) - return next; - - int start = (node->h % d->numBuckets) + 1; - Node **bucket = d->buckets + start; - int n = d->numBuckets - start; - while (n--) { - if (*bucket != e) - return *bucket; - ++bucket; - } - return e; -} - -QHashData::Node *QHashData::previousNode(Node *node) -{ - union { - Node *e; - QHashData *d; - }; - - e = node; - while (e->next) - e = e->next; - - int start; - if (node == e) - start = d->numBuckets - 1; - else - start = node->h % d->numBuckets; - - Node *sentinel = node; - Node **bucket = d->buckets + start; - while (start >= 0) { - if (*bucket != sentinel) { - Node *prev = *bucket; - while (prev->next != sentinel) - prev = prev->next; - return prev; - } - - sentinel = e; - --bucket; - --start; - } - Q_ASSERT_X(start >= 0, "QHash", "Iterating backward beyond begin()"); - return e; -} - -/* - If hint is negative, -hint gives the approximate number of - buckets that should be used for the hash table. If hint is - nonnegative, (1 << hint) gives the approximate number - of buckets that should be used. -*/ -void QHashData::rehash(int hint) -{ - if (hint < 0) { - hint = countBits(-hint); - if (hint < MinNumBits) - hint = MinNumBits; - userNumBits = hint; - while (primeForNumBits(hint) < (size >> 1)) - ++hint; - } else if (hint < MinNumBits) { - hint = MinNumBits; - } - - if (numBits != hint) { - Node *e = reinterpret_cast(this); - Node **oldBuckets = buckets; - int oldNumBuckets = numBuckets; - - int nb = primeForNumBits(hint); - buckets = new Node *[nb]; - numBits = hint; - numBuckets = nb; - for (int i = 0; i < numBuckets; ++i) - buckets[i] = e; - - for (int i = 0; i < oldNumBuckets; ++i) { - Node *firstNode = oldBuckets[i]; - while (firstNode != e) { - uint h = firstNode->h; - Node *lastNode = firstNode; - while (lastNode->next != e && lastNode->next->h == h) - lastNode = lastNode->next; - - Node *afterLastNode = lastNode->next; - Node **beforeFirstNode = &buckets[h % numBuckets]; - while (*beforeFirstNode != e) - beforeFirstNode = &(*beforeFirstNode)->next; - lastNode->next = *beforeFirstNode; - *beforeFirstNode = firstNode; - firstNode = afterLastNode; - } - } - delete [] oldBuckets; - } -} - -#ifdef QT_QHASH_DEBUG - -void QHashData::dump() -{ - qDebug("Hash data (ref = %d, size = %d, nodeSize = %d, userNumBits = %d, numBits = %d, numBuckets = %d)", - int(ref), size, nodeSize, userNumBits, numBits, - numBuckets); - qDebug(" %p (fakeNode = %p)", this, fakeNext); - for (int i = 0; i < numBuckets; ++i) { - Node *n = buckets[i]; - if (n != reinterpret_cast(this)) { - QString line = QString::asprintf("%d:", i); - while (n != reinterpret_cast(this)) { - line += QString::asprintf(" -> [%p]", n); - if (!n) { - line += " (CORRUPT)"; - break; - } - n = n->next; - } - qDebug("%ls", qUtf16Printable(line)); - } - } -} - -void QHashData::checkSanity() -{ - if (Q_UNLIKELY(fakeNext)) - qFatal("Fake next isn't 0"); - - for (int i = 0; i < numBuckets; ++i) { - Node *n = buckets[i]; - Node *p = n; - if (Q_UNLIKELY(!n)) - qFatal("%d: Bucket entry is 0", i); - if (n != reinterpret_cast(this)) { - while (n != reinterpret_cast(this)) { - if (Q_UNLIKELY(!n->next)) - qFatal("%d: Next of %p is 0, should be %p", i, n, this); - n = n->next; - } - } - } -} -#endif - /*! \fn template uint qHash(const QPair &key, uint seed = 0) \since 5.0 diff --git a/src/corelib/tools/qhash.h b/src/corelib/tools/qhash.h index d03f301306..cdf5577fcb 100644 --- a/src/corelib/tools/qhash.h +++ b/src/corelib/tools/qhash.h @@ -1,7 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -41,218 +40,769 @@ #ifndef QHASH_H #define QHASH_H -#include #include -#include +#include #include #include #include +#include -#include #include -#if defined(Q_CC_MSVC) -#pragma warning( push ) -#pragma warning( disable : 4311 ) // disable pointer truncation warning -#pragma warning( disable : 4127 ) // conditional expression is constant -#endif - QT_BEGIN_NAMESPACE -struct Q_CORE_EXPORT QHashData -{ - struct Node { - Node *next; - uint h; - }; - - Node *fakeNext; - Node **buckets; - QtPrivate::RefCount ref; - int size; - int nodeSize; - short userNumBits; - short numBits; - int numBuckets; - uint seed; - uint sharable : 1; - uint strictAlignment : 1; - uint reserved : 30; - - void *allocateNode(int nodeAlign); - void freeNode(void *node); - QHashData *detach_helper(void (*node_duplicate)(Node *, void *), void (*node_delete)(Node *), - int nodeSize, int nodeAlign); - bool willGrow(); - void hasShrunk(); - void rehash(int hint); - void free_helper(void (*node_delete)(Node *)); - Node *firstNode(); -#ifdef QT_QHASH_DEBUG - void dump(); - void checkSanity(); -#endif - static Node *nextNode(Node *node); - static Node *previousNode(Node *node); - - static const QHashData shared_null; -}; - -inline bool QHashData::willGrow() -{ - if (size >= numBuckets) { - rehash(numBits + 1); - return true; - } else { - return false; - } -} - -inline void QHashData::hasShrunk() -{ - if (size <= (numBuckets >> 3) && numBits > userNumBits) { - QT_TRY { - rehash(qMax(int(numBits) - 2, int(userNumBits))); - } QT_CATCH(const std::bad_alloc &) { - // ignore bad allocs - shrinking shouldn't throw. rehash is exception safe. - } - } -} - -inline QHashData::Node *QHashData::firstNode() -{ - Node *e = reinterpret_cast(this); - Node **bucket = buckets; - int n = numBuckets; - while (n--) { - if (*bucket != e) - return *bucket; - ++bucket; - } - return e; -} - struct QHashDummyValue { + bool operator==(const QHashDummyValue &) const noexcept { return true; } }; -constexpr bool operator==(const QHashDummyValue &, const QHashDummyValue &) noexcept +namespace QHashPrivate { + +// QHash uses a power of two growth policy. +namespace GrowthPolicy { - return true; +inline constexpr size_t maxNumBuckets() noexcept +{ + return size_t(1) << (8*sizeof(size_t) - 1); +} +inline constexpr size_t bucketsForCapacity(size_t requestedCapacity) noexcept +{ + if (requestedCapacity <= 8) + return 16; + if (requestedCapacity >= maxNumBuckets()) + return maxNumBuckets(); + return qNextPowerOfTwo(QIntegerForSize::Unsigned(2*requestedCapacity - 1)); +} +inline constexpr size_t bucketForHash(size_t nBuckets, size_t hash) noexcept +{ + return hash & (nBuckets - 1); +} } -Q_DECLARE_TYPEINFO(QHashDummyValue, Q_MOVABLE_TYPE | Q_DUMMY_TYPE); - -template -struct QHashNode +template +struct Node { - QHashNode *next; - const uint h; - const Key key; + using KeyType = Key; + using ValueType = T; + + Key key; T value; - - inline QHashNode(const Key &key0, const T &value0, uint hash, QHashNode *n) - : next(n), h(hash), key(key0), value(value0) {} - inline bool same_key(uint h0, const Key &key0) const { return h0 == h && key0 == key; } - -private: - Q_DISABLE_COPY(QHashNode) + static Node create(Key &&k, T &&t) noexcept(std::is_nothrow_move_assignable_v && std::is_nothrow_move_assignable_v) + { + return Node{ std::move(k), std::move(t) }; + } + static Node create(const Key &k, const T &t) noexcept(std::is_nothrow_copy_constructible_v && std::is_nothrow_copy_constructible_v) + { + return Node{ k, t }; + } + void replace(const T &t) noexcept(std::is_nothrow_assignable_v) + { + value = t; + } + void replace(T &&t) noexcept(std::is_nothrow_move_assignable_v) + { + value = std::move(t); + } + T &&takeValue() noexcept(std::is_nothrow_move_assignable_v) + { + return std::move(value); + } }; -// Specialize for QHashDummyValue in order to save some memory -template -struct QHashNode +template +struct MultiNodeChain { - union { - QHashNode *next; - QHashDummyValue value; - }; - const uint h; - const Key key; - - inline QHashNode(const Key &key0, const QHashDummyValue &, uint hash, QHashNode *n) - : next(n), h(hash), key(key0) {} - inline bool same_key(uint h0, const Key &key0) const { return h0 == h && key0 == key; } - -private: - Q_DISABLE_COPY(QHashNode) + T value; + MultiNodeChain *next = nullptr; + ~MultiNodeChain() + { + } + qsizetype free() noexcept(std::is_nothrow_destructible_v) + { + qsizetype nEntries = 0; + MultiNodeChain *e = this; + while (e) { + MultiNodeChain *n = e->next; + ++nEntries; + delete e; + e = n; + } + return nEntries; + } + bool contains(const T &val) const noexcept + { + const MultiNodeChain *e = this; + while (e) { + if (e->value == val) + return true; + e = e->next; + } + return false; + } }; +template +struct MultiNode +{ + using KeyType = Key; + using ValueType = T; + using Chain = MultiNodeChain; -#if 0 -// ### -// The introduction of the QHash random seed breaks this optimization, as it -// relies on qHash(int i) = i. If the hash value is salted, then the hash -// table becomes corrupted. -// -// A bit more research about whether it makes sense or not to salt integer -// keys (and in general keys whose hash value is easy to invert) -// is needed, or about how keep this optimization and the seed (f.i. by -// specializing QHash for integer values, and re-apply the seed during lookup). -// -// Be aware that such changes can easily be binary incompatible, and therefore -// cannot be made during the Qt 5 lifetime. -#define Q_HASH_DECLARE_INT_NODES(key_type) \ - template \ - struct QHashDummyNode { \ - QHashDummyNode *next; \ - union { uint h; key_type key; }; \ -\ - inline QHashDummyNode(key_type /* key0 */) {} \ - }; \ -\ - template \ - struct QHashNode { \ - QHashNode *next; \ - union { uint h; key_type key; }; \ - T value; \ -\ - inline QHashNode(key_type /* key0 */) {} \ - inline QHashNode(key_type /* key0 */, const T &value0) : value(value0) {} \ - inline bool same_key(uint h0, key_type) const { return h0 == h; } \ + Key key; + Chain *value; + + static MultiNode create(Key &&k, T &&t) + { + Chain *c = new Chain{ std::move(t), nullptr }; + return MultiNode(std::move(k), c); + } + static MultiNode create(const Key &k, const T &t) + { + Chain *c = new Chain{ t, nullptr }; + return MultiNode(k, c); } -#if defined(Q_BYTE_ORDER) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN -Q_HASH_DECLARE_INT_NODES(short); -Q_HASH_DECLARE_INT_NODES(ushort); -#endif -Q_HASH_DECLARE_INT_NODES(int); -Q_HASH_DECLARE_INT_NODES(uint); -#undef Q_HASH_DECLARE_INT_NODES -#endif // #if 0 + MultiNode(const Key &k, Chain *c) + : key(k), + value(c) + {} + MultiNode(Key &&k, Chain *c) noexcept(std::is_nothrow_move_assignable_v) + : key(std::move(k)), + value(c) + {} + + MultiNode(MultiNode &&other) + : key(other.key), + value(other.value) + { + other.value = nullptr; + } + + MultiNode(const MultiNode &other) + : key(other.key) + { + Chain *c = other.value; + Chain **e = &value; + while (c) { + Chain *chain = new Chain{ c->value, nullptr }; + *e = chain; + e = &chain->next; + c = c->next; + } + } + ~MultiNode() + { + if (value) + value->free(); + } + static qsizetype freeChain(MultiNode *n) noexcept(std::is_nothrow_destructible_v) + { + qsizetype size = n->value->free(); + n->value = nullptr; + return size; + } + void replace(const T &t) noexcept(std::is_nothrow_assignable_v) + { + value->value = t; + } + void replace(T &&t) noexcept(std::is_nothrow_move_assignable_v) + { + value->value = std::move(t); + } + void insertMulti(const T &t) + { + Chain *e = new Chain{ t, nullptr }; + e->next = value; + value = e; + } + + // compiler generated move operators are fine +}; + +template +constexpr bool isRelocatable() +{ + return QTypeInfo::isRelocatable && QTypeInfo::isRelocatable; +} + +// Regular hash tables consist of a list of buckets that can store Nodes. But simply allocating one large array of buckets +// would waste a lot of memory. To avoid this, we split the vector of buckets up into a vector of Spans. Each Span represents +// NEntries buckets. To quickly find the correct Span that holds a bucket, NEntries must be a power of two. +// +// Inside each Span, there is an offset array that represents the actual buckets. offsets contains either an index into the +// actual storage space for the Nodes (the 'entries' member) or 0xff (UnusedEntry) to flag that the bucket is empty. +// As we have only 128 entries per Span, the offset array can be represented using an unsigned char. This trick makes the hash +// table have a very small memory overhead compared to many other implementations. +template +struct Span { + enum { + NEntries = 128, + LocalBucketMask = (NEntries - 1), + UnusedEntry = 0xff + }; + static_assert ((NEntries & LocalBucketMask) == 0, "EntriesPerSpan must be a power of two."); + + // Entry is a slot available for storing a Node. The Span holds a pointer to + // an array of Entries. Upon construction of the array, those entries are + // unused, and nextFree() is being used to set up a singly linked list + // of free entries. + // When a node gets inserted, the first free entry is being picked, removed + // from the singly linked list and the Node gets constructed in place. + struct Entry { + typename std::aligned_storage::type storage; + + unsigned char &nextFree() { return *reinterpret_cast(&storage); } + Node &node() { return *reinterpret_cast(&storage); } + }; + + unsigned char offsets[NEntries]; + Entry *entries = nullptr; + unsigned char allocated = 0; + unsigned char nextFree = 0; + Span() noexcept + { + memset(offsets, UnusedEntry, sizeof(offsets)); + } + ~Span() + { + freeData(); + } + void freeData() noexcept(std::is_nothrow_destructible::value) + { + if (entries) { + if constexpr (!std::is_trivially_destructible::value) { + for (auto o : offsets) { + if (o != UnusedEntry) + entries[o].node().~Node(); + } + } + delete [] entries; + entries = nullptr; + } + } + void insert(size_t i, Node &&n) + { + Q_ASSERT(i <= NEntries); + Q_ASSERT(offsets[i] == UnusedEntry); + if (nextFree == allocated) + addStorage(); + unsigned char entry = nextFree; + Q_ASSERT(entry < allocated); + nextFree = entries[entry].nextFree(); + offsets[i] = entry; + new (&entries[entry].node()) Node(std::move(n)); + } + void erase(size_t bucket) noexcept(std::is_nothrow_destructible::value) + { + Q_ASSERT(bucket <= NEntries); + Q_ASSERT(offsets[bucket] != UnusedEntry); + + unsigned char entry = offsets[bucket]; + offsets[bucket] = UnusedEntry; + + entries[entry].node().~Node(); + entries[entry].nextFree() = nextFree; + nextFree = entry; + } + size_t offset(size_t i) const noexcept + { + return offsets[i]; + } + bool hasNode(size_t i) const noexcept + { + return (offsets[i] != UnusedEntry); + } + Node &at(size_t i) noexcept + { + Q_ASSERT(i <= NEntries); + Q_ASSERT(offsets[i] != UnusedEntry); + + return entries[offsets[i]].node(); + } + const Node &at(size_t i) const noexcept + { + Q_ASSERT(i <= NEntries); + Q_ASSERT(offsets[i] != UnusedEntry); + + return entries[offsets[i]].node(); + } + Node &atOffset(size_t o) noexcept + { + Q_ASSERT(o < allocated); + + return entries[o].node(); + } + const Node &atOffset(size_t o) const noexcept + { + Q_ASSERT(o < allocated); + + return entries[o].node(); + } + void moveLocal(size_t from, size_t to) noexcept + { + Q_ASSERT(offsets[from] != UnusedEntry); + Q_ASSERT(offsets[to] == UnusedEntry); + offsets[to] = offsets[from]; + offsets[from] = UnusedEntry; + } + void moveFromSpan(Span &fromSpan, size_t fromIndex, size_t to) noexcept(std::is_nothrow_move_constructible_v) + { + Q_ASSERT(to <= NEntries); + Q_ASSERT(offsets[to] == UnusedEntry); + Q_ASSERT(fromIndex <= NEntries); + Q_ASSERT(fromSpan.offsets[fromIndex] != UnusedEntry); + if (nextFree == allocated) + addStorage(); + Q_ASSERT(nextFree < allocated); + offsets[to] = nextFree; + Entry &toEntry = entries[nextFree]; + nextFree = toEntry.nextFree(); + + size_t fromOffset = fromSpan.offsets[fromIndex]; + fromSpan.offsets[fromIndex] = UnusedEntry; + Entry &fromEntry = fromSpan.entries[fromOffset]; + + if constexpr (isRelocatable()) { + memcpy(&toEntry, &fromEntry, sizeof(Entry)); + } else { + new (&toEntry.node()) Node(std::move(fromEntry.node())); + fromEntry.node().~Node(); + } + fromEntry.nextFree() = fromSpan.nextFree; + fromSpan.nextFree = static_cast(fromOffset); + } + + void addStorage() + { + Q_ASSERT(allocated < NEntries); + Q_ASSERT(nextFree == allocated); + // the hash table should always be between 25 and 50% full + // this implies that we on average have between 32 and 64 entries + // in here. The likelihood of having below 16 entries is very small, + // so start with that and increment by 16 each time we need to add + // some more space + const size_t increment = NEntries/8; + size_t alloc = allocated + increment; + Entry *newEntries = new Entry[alloc]; + // we only add storage if the previous storage was fully filled, so + // simply copy the old data over + if constexpr (isRelocatable()) { + memcpy(newEntries, entries, allocated*sizeof(Entry)); + } else { + for (size_t i = 0; i < allocated; ++i) { + new (&newEntries[i].node()) Node(std::move(entries[i].node())); + entries[i].node().~Node(); + } + } + for (size_t i = allocated; i < allocated + increment; ++i) { + newEntries[i].nextFree() = uchar(i + 1); + } + delete [] entries; + entries = newEntries; + allocated = uchar(alloc); + } +}; + +template +struct iterator; + +template +struct Data +{ + using Key = typename Node::KeyType; + using T = typename Node::ValueType; + using Span = QHashPrivate::Span; + using iterator = QHashPrivate::iterator; + + QtPrivate::RefCount ref = {{1}}; + size_t size = 0; + size_t numBuckets = 0; + size_t seed = 0; + + + Span *spans = nullptr; + + Data(size_t reserve = 0) + { + numBuckets = GrowthPolicy::bucketsForCapacity(reserve); + size_t nSpans = (numBuckets + Span::LocalBucketMask) / Span::NEntries; + spans = new Span[nSpans]; + seed = qGlobalQHashSeed(); + } + Data(const Data &other) + : size(other.size), + numBuckets(other.numBuckets), + seed(other.seed) + { + size_t nSpans = (other.numBuckets + Span::LocalBucketMask) / Span::NEntries; + spans = new Span[nSpans]; + + for (size_t s = 0; s < nSpans; ++s) { + const Span &span = other.spans[s]; + for (size_t index = 0; index < Span::NEntries; ++index) { + if (!span.hasNode(index)) + continue; + const Node &n = span.at(index); + iterator it{ this, s*Span::NEntries + index }; + Q_ASSERT(it.isUnused()); + + spans[it.span()].insert(it.index(), std::move(Node(n))); + } + } + } + Data(const Data &other, size_t reserved) + : size(other.size), + seed(other.seed) + { + numBuckets = GrowthPolicy::bucketsForCapacity(qMax(size, reserved)); + size_t nSpans = (numBuckets + Span::LocalBucketMask) / Span::NEntries; + spans = new Span[nSpans]; + + for (size_t s = 0; s < nSpans; ++s) { + const Span &span = other.spans[s]; + for (size_t index = 0; index < Span::NEntries; ++index) { + if (!span.hasNode(index)) + continue; + const Node &n = span.at(index); + iterator it = find(n.key); + Q_ASSERT(it.isUnused()); + spans[it.span()].insert(it.index(), std::move(Node(n))); + } + } + } + + static Data *detached(Data *d) + { + if (!d) + return new Data; + Data *dd = new Data(*d); + if (!d->ref.deref()) + delete d; + return dd; + } + static Data *detached(Data *d, size_t size) + { + if (!d) + return new Data(size); + Data *dd = new Data(*d, size); + if (!d->ref.deref()) + delete d; + return dd; + } + + + iterator detachedIterator(iterator other) const noexcept + { + return iterator{this, other.bucket}; + } + + iterator begin() const noexcept + { + iterator it{ this, 0 }; + if (it.isUnused()) + ++it; + return it; + } + + constexpr iterator end() const noexcept + { + return iterator(); + } + + void rehash(size_t sizeHint = 0) + { + if (sizeHint == 0) + sizeHint = size; + size_t newBucketCount = GrowthPolicy::bucketsForCapacity(sizeHint); + + Span *oldSpans = spans; + size_t oldBucketCount = numBuckets; + size_t nSpans = (newBucketCount + Span::LocalBucketMask) / Span::NEntries; + spans = new Span[nSpans]; + numBuckets = newBucketCount; + size_t oldNSpans = (oldBucketCount + Span::LocalBucketMask) / Span::NEntries; + + for (size_t s = 0; s < oldNSpans; ++s) { + Span &span = oldSpans[s]; + for (size_t index = 0; index < Span::NEntries; ++index) { + if (!span.hasNode(index)) + continue; + Node &n = span.at(index); + iterator it = find(n.key); + Q_ASSERT(it.isUnused()); + spans[it.span()].insert(it.index(), std::move(n)); + } + span.freeData(); + } + delete [] oldSpans; + } + + size_t nextBucket(size_t bucket) const noexcept + { + ++bucket; + if (bucket == numBuckets) + bucket = 0; + return bucket; + } + + float loadFactor() const noexcept + { + return float(size)/numBuckets; + } + bool shouldGrow() const noexcept + { + return size >= (numBuckets >> 1); + } + + iterator find(const Key &key) const noexcept + { + Q_ASSERT(numBuckets > 0); + size_t hash = qHash(key, seed); + size_t bucket = GrowthPolicy::bucketForHash(numBuckets, hash); + // loop over the buckets until we find the entry we search for + // or an empty slot, in which case we know the entry doesn't exist + while (true) { + // Split the bucket into the indexex of span array, and the local + // offset inside the span + size_t span = bucket / Span::NEntries; + size_t index = bucket & Span::LocalBucketMask; + Span &s = spans[span]; + size_t offset = s.offset(index); + if (offset == Span::UnusedEntry) { + return iterator{ this, bucket }; + } else { + Node &n = s.atOffset(offset); + if (n.key == key) + return iterator{ this, bucket }; + } + bucket = nextBucket(bucket); + } + } + + Node *findNode(const Key &key) const noexcept + { + if (!size) + return nullptr; + iterator it = find(key); + if (it.isUnused()) + return nullptr; + return it.node(); + } + + Node *findAndInsertNode(const Key &key) noexcept + { + if (shouldGrow()) + rehash(size + 1); + iterator it = find(key); + if (it.isUnused()) { + spans[it.span()].insert(it.index(), Node::create(key, T())); + ++size; + } + return it.node(); + } + + + iterator insert(Node &&n) + { + if (shouldGrow()) + rehash(size + 1); + iterator it = find(n.key); + if (it.isUnused()) { + spans[it.span()].insert(it.index(), std::move(n)); + ++size; + } else { + it.node()->replace(std::move(n.takeValue())); + } + return it; + } + + iterator insert(const Key &key, const T &value) + { + if (shouldGrow()) + rehash(size + 1); + auto it = find(key); + if (it.isUnused()) { + spans[it.span()].insert(it.index(), Node::create(key, value)); + ++size; + } else { + it.node()->replace(value); + } + return it; + } + + iterator insertMulti(const Key &key, const T &value) + { + if (shouldGrow()) + rehash(size + 1); + auto it = find(key); + if (it.isUnused()) { + spans[it.span()].insert(it.index(), std::move(Node::create(key, value))); + ++size; + } else { + it.node()->insertMulti(value); + } + return it; + } + + + iterator erase(iterator it) noexcept(std::is_nothrow_destructible::value) + { + size_t bucket = it.bucket; + size_t span = bucket / Span::NEntries; + size_t index = bucket & Span::LocalBucketMask; + Q_ASSERT(spans[span].hasNode(index)); + spans[span].erase(index); + --size; + + // re-insert the following entries to avoid holes + size_t hole = bucket; + size_t next = bucket; + while (true) { + next = nextBucket(next); + size_t nextSpan = next / Span::NEntries; + size_t nextIndex = next & Span::LocalBucketMask; + if (!spans[nextSpan].hasNode(nextIndex)) + break; + size_t hash = qHash(spans[nextSpan].at(nextIndex).key, seed); + size_t newBucket = GrowthPolicy::bucketForHash(numBuckets, hash); + while (true) { + if (newBucket == next) { + // nothing to do, item is at the right plae + break; + } else if (newBucket == hole) { + // move into hole + size_t holeSpan = hole / Span::NEntries; + size_t holeIndex = hole & Span::LocalBucketMask; + if (nextSpan == holeSpan) { + spans[holeSpan].moveLocal(nextIndex, holeIndex); + } else { + // move between spans, more expensive + spans[holeSpan].moveFromSpan(spans[nextSpan], nextIndex, holeIndex); + } + hole = next; + break; + } + newBucket = nextBucket(newBucket); + } + } + + // return correct position of the next element + if (!spans[span].hasNode(index)) + ++it; + return it; + } + + ~Data() + { + delete [] spans; + } +}; + +template +struct iterator { + using Span = QHashPrivate::Span; + + const Data *d = nullptr; + size_t bucket = 0; + + size_t span() const noexcept { return bucket / Span::NEntries; } + size_t index() const noexcept { return bucket & Span::LocalBucketMask; } + inline bool isUnused() const noexcept { return !d->spans[span()].hasNode(index()); } + + inline Node *node() const noexcept + { + Q_ASSERT(!isUnused()); + return &d->spans[span()].at(index()); + } + bool atEnd() const noexcept { return !d; } + + iterator operator++() noexcept + { + while (true) { + ++bucket; + if (bucket == d->numBuckets) { + d = nullptr; + bucket = 0; + break; + } + if (!isUnused()) + break; + } + return *this; + } + bool operator==(iterator other) const noexcept + { return d == other.d && bucket == other.bucket; } + bool operator!=(iterator other) const noexcept + { return !(*this == other); } +}; + + + +} // namespace QHashPrivate template class QHash { - typedef QHashNode Node; + using Node = QHashPrivate::Node; + using Data = QHashPrivate::Data; + friend class QSet; - union { - QHashData *d; - QHashNode *e; - }; - - static inline Node *concrete(QHashData::Node *node) { - return reinterpret_cast(node); - } - - static inline int alignOfNode() { return qMax(sizeof(void*), alignof(Node)); } + Data *d = nullptr; public: - inline QHash() noexcept : d(const_cast(&QHashData::shared_null)) { } + using key_type = Key; + using mapped_type = T; + using value_type = T; + using size_type = qsizetype; + using difference_type = qsizetype; + using reference = T &; + using const_reference = const T &; + + inline QHash() noexcept = default; inline QHash(std::initializer_list > list) - : d(const_cast(&QHashData::shared_null)) + : d(new Data(list.size())) { - reserve(int(list.size())); for (typename std::initializer_list >::const_iterator it = list.begin(); it != list.end(); ++it) insert(it->first, it->second); } - QHash(const QHash &other) : d(other.d) { d->ref.ref(); if (!d->sharable) detach(); } - ~QHash() { if (!d->ref.deref()) freeData(d); } + QHash(const QHash &other) noexcept + : d(other.d) + { + if (d) + d->ref.ref(); + } + ~QHash() + { + if (d && !d->ref.deref()) + delete d; + } - QHash &operator=(const QHash &other); - QHash(QHash &&other) noexcept : d(other.d) { other.d = const_cast(&QHashData::shared_null); } - QHash &operator=(QHash &&other) noexcept - { QHash moved(std::move(other)); swap(moved); return *this; } + QHash &operator=(const QHash &other) noexcept(std::is_nothrow_destructible::value) + { + if (d != other.d) { + Data *o = other.d; + if (o) + o->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = o; + } + return *this; + } + + QHash(QHash &&other) noexcept + : d(std::exchange(other.d, nullptr)) + { + } + QHash &operator=(QHash &&other) noexcept(std::is_nothrow_destructible::value) + { + if (d != other.d) { + if (d && !d->ref.deref()) + delete d; + d = std::exchange(other.d, nullptr); + } + return *this; + } #ifdef Q_QDOC template QHash(InputIterator f, InputIterator l); @@ -277,170 +827,220 @@ public: #endif void swap(QHash &other) noexcept { qSwap(d, other.d); } - bool operator==(const QHash &other) const; - bool operator!=(const QHash &other) const { return !(*this == other); } + bool operator==(const QHash &other) const noexcept + { + if (d == other.d) + return true; + if (size() != other.size()) + return false; - inline int size() const { return d->size; } + for (const_iterator it = other.begin(); it != other.end(); ++it) { + const_iterator i = find(it.key()); + if (i == end() || !(i.value() == it.value())) + return false; + } + // all values must be the same as size is the same + return true; + } + bool operator!=(const QHash &other) const noexcept { return !(*this == other); } - inline bool isEmpty() const { return d->size == 0; } + inline qsizetype size() const noexcept { return d ? qsizetype(d->size) : 0; } + inline bool isEmpty() const noexcept { return !d || d->size == 0; } - inline int capacity() const { return d->numBuckets; } - void reserve(int size); - inline void squeeze() { reserve(1); } + inline qsizetype capacity() const noexcept { return d ? qsizetype(d->numBuckets >> 1) : 0; } + void reserve(qsizetype size) + { + if (isDetached()) + d->rehash(size); + else + d = Data::detached(d, size_t(size)); + } + inline void squeeze() { reserve(0); } - inline void detach() { if (d->ref.isShared()) detach_helper(); } - inline bool isDetached() const { return !d->ref.isShared(); } - bool isSharedWith(const QHash &other) const { return d == other.d; } + inline void detach() { if (!d || d->ref.isShared()) d = Data::detached(d); } + inline bool isDetached() const noexcept { return d && !d->ref.isShared(); } + bool isSharedWith(const QHash &other) const noexcept { return d == other.d; } - void clear(); + void clear() noexcept(std::is_nothrow_destructible::value) + { + if (d && !d->ref.deref()) + delete d; + d = nullptr; + } - int remove(const Key &key); - T take(const Key &key); + bool remove(const Key &key) + { + if (isEmpty()) // prevents detaching shared null + return false; + detach(); - bool contains(const Key &key) const; - Key key(const T &value) const; - Key key(const T &value, const Key &defaultKey) const; - T value(const Key &key) const; - T value(const Key &key, const T &defaultValue) const; - T &operator[](const Key &key); - const T operator[](const Key &key) const; + auto it = d->find(key); + if (it.isUnused()) + return false; + d->erase(it); + return true; + } + T take(const Key &key) + { + if (isEmpty()) // prevents detaching shared null + return T(); + detach(); - QList keys() const; - QList keys(const T &value) const; - QList values() const; -#if QT_DEPRECATED_SINCE(5, 15) - QT_DEPRECATED_VERSION_X_5_15("Use QMultiHash for hashes storing multiple values with the same key.") QList uniqueKeys() const; - QT_DEPRECATED_VERSION_X_5_15("Use QMultiHash for hashes storing multiple values with the same key.") QList values(const Key &key) const; -#endif - int count(const Key &key) const; + auto it = d->find(key); + if (it.isUnused()) + return T(); + T value = it.node()->takeValue(); + d->erase(it); + return value; + } + + bool contains(const Key &key) const noexcept + { + if (!d) + return false; + return d->findNode(key) != nullptr; + } + qsizetype count(const Key &key) const noexcept + { + return contains(key) ? 1 : 0; + } + + Key key(const T &value, const Key &defaultKey = Key()) const noexcept + { + if (d) { + const_iterator i = begin(); + while (i != end()) { + if (i.value() == value) + return i.key(); + ++i; + } + } + + return defaultKey; + } + T value(const Key &key, const T &defaultValue = T()) const noexcept + { + if (d) { + Node *n = d->findNode(key); + if (n) + return n->value; + } + return defaultValue; + } + T &operator[](const Key &key) + { + detach(); + Node *n = d->findAndInsertNode(key); + Q_ASSERT(n); + return n->value; + } + + const T operator[](const Key &key) const noexcept + { + return value(key); + } + + QVector keys() const + { + return QVector(keyBegin(), keyEnd()); + } + QVector keys(const T &value) const + { + QVector res; + const_iterator i = begin(); + while (i != end()) { + if (i.value() == value) + res.append(i.key()); + ++i; + } + return res; + } + QVector values() const + { + return QVector(begin(), end()); + } class const_iterator; class iterator { + using piter = typename QHashPrivate::iterator; friend class const_iterator; friend class QHash; friend class QSet; - QHashData::Node *i; + piter i; + explicit inline iterator(piter it) noexcept : i(it) { } public: -#if QT_DEPRECATED_WARNINGS_SINCE < QT_VERSION_CHECK(5, 15, 0) - typedef std::bidirectional_iterator_tag iterator_category; -#else typedef std::forward_iterator_tag iterator_category; -#endif typedef qptrdiff difference_type; typedef T value_type; typedef T *pointer; typedef T &reference; - inline iterator() : i(nullptr) { } - explicit inline iterator(void *node) : i(reinterpret_cast(node)) { } + constexpr iterator() noexcept = default; - inline const Key &key() const { return concrete(i)->key; } - inline T &value() const { return concrete(i)->value; } - inline T &operator*() const { return concrete(i)->value; } - inline T *operator->() const { return &concrete(i)->value; } - inline bool operator==(const iterator &o) const { return i == o.i; } - inline bool operator!=(const iterator &o) const { return i != o.i; } + inline const Key &key() const noexcept { return i.node()->key; } + inline T &value() const noexcept { return i.node()->value; } + inline T &operator*() const noexcept { return i.node()->value; } + inline T *operator->() const noexcept { return &i.node()->value; } + inline bool operator==(const iterator &o) const noexcept { return i == o.i; } + inline bool operator!=(const iterator &o) const noexcept { return i != o.i; } - inline iterator &operator++() { - i = QHashData::nextNode(i); + inline iterator &operator++() noexcept + { + ++i; return *this; } - inline iterator operator++(int) { - iterator r = *this; - i = QHashData::nextNode(i); - return r; - } -#if QT_DEPRECATED_SINCE(5, 15) - inline QT_DEPRECATED_VERSION_5_15 iterator &operator--() - { - i = QHashData::previousNode(i); - return *this; - } - inline QT_DEPRECATED_VERSION_5_15 iterator operator--(int) + inline iterator operator++(int) noexcept { iterator r = *this; - i = QHashData::previousNode(i); + ++i; return r; } - inline QT_DEPRECATED_VERSION_5_15 iterator operator+(int j) const - { iterator r = *this; if (j > 0) while (j--) ++r; else while (j++) --r; return r; } - inline QT_DEPRECATED_VERSION_5_15 iterator operator-(int j) const { return operator+(-j); } - inline QT_DEPRECATED_VERSION_5_15 iterator &operator+=(int j) { return *this = *this + j; } - inline QT_DEPRECATED_VERSION_5_15 iterator &operator-=(int j) { return *this = *this - j; } - friend inline QT_DEPRECATED_VERSION_5_15 iterator operator+(int j, iterator k) { return k + j; } -#endif - inline bool operator==(const const_iterator &o) const { return i == o.i; } - inline bool operator!=(const const_iterator &o) const { return i != o.i; } + inline bool operator==(const const_iterator &o) const noexcept { return i == o.i; } + inline bool operator!=(const const_iterator &o) const noexcept { return i != o.i; } }; friend class iterator; class const_iterator { + using piter = typename QHashPrivate::iterator; friend class iterator; friend class QHash; - friend class QMultiHash; friend class QSet; - QHashData::Node *i; + piter i; + explicit inline const_iterator(piter it) : i(it) { } public: -#if QT_DEPRECATED_WARNINGS_SINCE < QT_VERSION_CHECK(5, 15, 0) - typedef std::bidirectional_iterator_tag iterator_category; -#else typedef std::forward_iterator_tag iterator_category; -#endif typedef qptrdiff difference_type; typedef T value_type; typedef const T *pointer; typedef const T &reference; - Q_DECL_CONSTEXPR inline const_iterator() : i(nullptr) { } - explicit inline const_iterator(void *node) - : i(reinterpret_cast(node)) { } - inline const_iterator(const iterator &o) - { i = o.i; } + constexpr const_iterator() noexcept = default; + inline const_iterator(const iterator &o) noexcept : i(o.i) { } - inline const Key &key() const { return concrete(i)->key; } - inline const T &value() const { return concrete(i)->value; } - inline const T &operator*() const { return concrete(i)->value; } - inline const T *operator->() const { return &concrete(i)->value; } - Q_DECL_CONSTEXPR inline bool operator==(const const_iterator &o) const { return i == o.i; } - Q_DECL_CONSTEXPR inline bool operator!=(const const_iterator &o) const { return i != o.i; } + inline const Key &key() const noexcept { return i.node()->key; } + inline const T &value() const noexcept { return i.node()->value; } + inline const T &operator*() const noexcept { return i.node()->value; } + inline const T *operator->() const noexcept { return &i.node()->value; } + inline bool operator==(const const_iterator &o) const noexcept { return i == o.i; } + inline bool operator!=(const const_iterator &o) const noexcept { return i != o.i; } - inline const_iterator &operator++() { - i = QHashData::nextNode(i); + inline const_iterator &operator++() noexcept + { + ++i; return *this; } - inline const_iterator operator++(int) { - const_iterator r = *this; - i = QHashData::nextNode(i); - return r; - } -#if QT_DEPRECATED_SINCE(5, 15) - inline QT_DEPRECATED_VERSION_5_15 const_iterator &operator--() - { - i = QHashData::previousNode(i); - return *this; - } - inline QT_DEPRECATED_VERSION_5_15 const_iterator operator--(int) + inline const_iterator operator++(int) noexcept { const_iterator r = *this; - i = QHashData::previousNode(i); + ++i; return r; } - inline QT_DEPRECATED_VERSION_5_15 const_iterator operator+(int j) const - { const_iterator r = *this; if (j > 0) while (j--) ++r; else while (j++) --r; return r; } - inline QT_DEPRECATED_VERSION_5_15 const_iterator operator-(int j) const { return operator+(-j); } - inline QT_DEPRECATED_VERSION_5_15 const_iterator &operator+=(int j) { return *this = *this + j; } - inline QT_DEPRECATED_VERSION_5_15 const_iterator &operator-=(int j) { return *this = *this - j; } - friend inline QT_DEPRECATED_VERSION_5_15 const_iterator operator+(int j, const_iterator k) - { - return k + j; - } -#endif }; friend class const_iterator; @@ -450,561 +1050,155 @@ public: public: typedef typename const_iterator::iterator_category iterator_category; - typedef typename const_iterator::difference_type difference_type; + typedef qptrdiff difference_type; typedef Key value_type; typedef const Key *pointer; typedef const Key &reference; - key_iterator() = default; - explicit key_iterator(const_iterator o) : i(o) { } + key_iterator() noexcept = default; + explicit key_iterator(const_iterator o) noexcept : i(o) { } - const Key &operator*() const { return i.key(); } - const Key *operator->() const { return &i.key(); } - bool operator==(key_iterator o) const { return i == o.i; } - bool operator!=(key_iterator o) const { return i != o.i; } + const Key &operator*() const noexcept { return i.key(); } + const Key *operator->() const noexcept { return &i.key(); } + bool operator==(key_iterator o) const noexcept { return i == o.i; } + bool operator!=(key_iterator o) const noexcept { return i != o.i; } - inline key_iterator &operator++() { ++i; return *this; } - inline key_iterator operator++(int) { return key_iterator(i++);} -#if QT_DEPRECATED_SINCE(5, 15) - inline QT_DEPRECATED_VERSION_5_15 key_iterator &operator--() - { - --i; - return *this; - } - inline QT_DEPRECATED_VERSION_5_15 key_iterator operator--(int) { return key_iterator(i--); } -#endif - const_iterator base() const { return i; } + inline key_iterator &operator++() noexcept { ++i; return *this; } + inline key_iterator operator++(int) noexcept { return key_iterator(i++);} + const_iterator base() const noexcept { return i; } }; typedef QKeyValueIterator const_key_value_iterator; typedef QKeyValueIterator key_value_iterator; // STL style - inline iterator begin() { detach(); return iterator(d->firstNode()); } - inline const_iterator begin() const { return const_iterator(d->firstNode()); } - inline const_iterator cbegin() const { return const_iterator(d->firstNode()); } - inline const_iterator constBegin() const { return const_iterator(d->firstNode()); } - inline iterator end() { detach(); return iterator(e); } - inline const_iterator end() const { return const_iterator(e); } - inline const_iterator cend() const { return const_iterator(e); } - inline const_iterator constEnd() const { return const_iterator(e); } - inline key_iterator keyBegin() const { return key_iterator(begin()); } - inline key_iterator keyEnd() const { return key_iterator(end()); } + inline iterator begin() { detach(); return iterator(d->begin()); } + inline const_iterator begin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline const_iterator cbegin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline const_iterator constBegin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline iterator end() noexcept { return iterator(); } + inline const_iterator end() const noexcept { return const_iterator(); } + inline const_iterator cend() const noexcept { return const_iterator(); } + inline const_iterator constEnd() const noexcept { return const_iterator(); } + inline key_iterator keyBegin() const noexcept { return key_iterator(begin()); } + inline key_iterator keyEnd() const noexcept { return key_iterator(end()); } inline key_value_iterator keyValueBegin() { return key_value_iterator(begin()); } inline key_value_iterator keyValueEnd() { return key_value_iterator(end()); } - inline const_key_value_iterator keyValueBegin() const { return const_key_value_iterator(begin()); } - inline const_key_value_iterator constKeyValueBegin() const { return const_key_value_iterator(begin()); } - inline const_key_value_iterator keyValueEnd() const { return const_key_value_iterator(end()); } - inline const_key_value_iterator constKeyValueEnd() const { return const_key_value_iterator(end()); } + inline const_key_value_iterator keyValueBegin() const noexcept { return const_key_value_iterator(begin()); } + inline const_key_value_iterator constKeyValueBegin() const noexcept { return const_key_value_iterator(begin()); } + inline const_key_value_iterator keyValueEnd() const noexcept { return const_key_value_iterator(end()); } + inline const_key_value_iterator constKeyValueEnd() const noexcept { return const_key_value_iterator(end()); } - QPair equal_range(const Key &key); - QPair equal_range(const Key &key) const noexcept; - iterator erase(iterator it) { return erase(const_iterator(it.i)); } - iterator erase(const_iterator it); + iterator erase(const_iterator it) + { + Q_ASSERT(it != constEnd()); + detach(); + // ensure a valid iterator across the detach: + iterator i = iterator{d->detachedIterator(it.i)}; + + i.i = d->erase(i.i); + return i; + } + + QPair equal_range(const Key &key) + { + auto first = find(key); + auto second = first; + if (second != iterator()) + ++second; + return qMakePair(first, second); + } + + QPair equal_range(const Key &key) const noexcept + { + auto first = find(key); + auto second = first; + if (second != iterator()) + ++second; + return qMakePair(first, second); + } - // more Qt typedef iterator Iterator; typedef const_iterator ConstIterator; - inline int count() const { return d->size; } - iterator find(const Key &key); - const_iterator find(const Key &key) const; - const_iterator constFind(const Key &key) const; - iterator insert(const Key &key, const T &value); - void insert(const QHash &hash); -#if QT_DEPRECATED_SINCE(5, 15) - QT_DEPRECATED_VERSION_X_5_15("Use QMultiHash for hashes storing multiple values with the same key.") iterator insertMulti(const Key &key, const T &value); - QT_DEPRECATED_VERSION_X_5_15("Use QMultiHash for hashes storing multiple values with the same key.") QHash &unite(const QHash &other); -#endif - - // STL compatibility - typedef T mapped_type; - typedef Key key_type; - typedef qptrdiff difference_type; - typedef int size_type; - - inline bool empty() const { return isEmpty(); } - -#ifdef QT_QHASH_DEBUG - inline void dump() const { d->dump(); } - inline void checkSanity() const { d->checkSanity(); } -#endif - -private: - void detach_helper(); - void freeData(QHashData *d); - Node **findNode(const Key &key, uint *hp = nullptr) const; - Node **findNode(const Key &key, uint h) const; - Node *createNode(uint h, const Key &key, const T &value, Node **nextNode); - void deleteNode(Node *node); - static void deleteNode2(QHashData::Node *node); - - static void duplicateNode(QHashData::Node *originalNode, void *newNode); - - bool isValidIterator(const iterator &it) const noexcept - { return isValidNode(it.i); } - bool isValidIterator(const const_iterator &it) const noexcept - { return isValidNode(it.i); } - bool isValidNode(QHashData::Node *node) const noexcept + inline qsizetype count() const noexcept { return d ? qsizetype(d->size) : 0; } + iterator find(const Key &key) { -#if defined(QT_DEBUG) && !defined(Q_HASH_NO_ITERATOR_DEBUG) - while (node->next) - node = node->next; - return (static_cast(node) == d); -#else - Q_UNUSED(node); - return true; -#endif + if (isEmpty()) // prevents detaching shared null + return end(); + detach(); + auto it = d->find(key); + if (it.isUnused()) + it = d->end(); + return iterator(it); } - friend class QSet; - friend class QMultiHash; + const_iterator find(const Key &key) const noexcept + { + if (isEmpty()) + return end(); + auto it = d->find(key); + if (it.isUnused()) + it = d->end(); + return const_iterator(it); + } + const_iterator constFind(const Key &key) const noexcept + { + return find(key); + } + iterator insert(const Key &key, const T &value) + { + detach(); + + auto i = d->insert(Node{key, value}); + return iterator(i); + } + void insert(const QHash &hash) + { + if (d == hash.d || !hash.d) + return; + if (!d) { + *this = hash; + return; + } + + detach(); + + for (auto it = hash.begin(); it != hash.end(); ++it) + insert(it.key(), it.value()); + } + + float load_factor() const noexcept { return d ? d->loadFactor() : 0; } + static float max_load_factor() noexcept { return 0.5; } + size_t bucket_count() const noexcept { return d ? d->numBuckets : 0; } + static size_t max_bucket_count() noexcept { return QHashPrivate::GrowthPolicy::maxNumBuckets(); } + + inline bool empty() const noexcept { return isEmpty(); } }; -template -Q_INLINE_TEMPLATE void QHash::deleteNode(Node *node) -{ - deleteNode2(reinterpret_cast(node)); - d->freeNode(node); -} template -Q_INLINE_TEMPLATE void QHash::deleteNode2(QHashData::Node *node) +class QMultiHash { -#ifdef Q_CC_BOR - concrete(node)->~QHashNode(); -#else - concrete(node)->~Node(); -#endif -} + using Node = QHashPrivate::MultiNode; + using Data = QHashPrivate::Data; + using Chain = QHashPrivate::MultiNodeChain; -template -Q_INLINE_TEMPLATE void QHash::duplicateNode(QHashData::Node *node, void *newNode) -{ - Node *concreteNode = concrete(node); - new (newNode) Node(concreteNode->key, concreteNode->value, concreteNode->h, nullptr); -} + Data *d = nullptr; + qsizetype m_size = 0; -template -Q_INLINE_TEMPLATE typename QHash::Node * -QHash::createNode(uint ah, const Key &akey, const T &avalue, Node **anextNode) -{ - Node *node = new (d->allocateNode(alignOfNode())) Node(akey, avalue, ah, *anextNode); - *anextNode = node; - ++d->size; - return node; -} - -template -Q_OUTOFLINE_TEMPLATE void QHash::freeData(QHashData *x) -{ - x->free_helper(deleteNode2); -} - -template -Q_INLINE_TEMPLATE void QHash::clear() -{ - *this = QHash(); -} - -template -Q_OUTOFLINE_TEMPLATE void QHash::detach_helper() -{ - QHashData *x = d->detach_helper(duplicateNode, deleteNode2, sizeof(Node), alignOfNode()); - if (!d->ref.deref()) - freeData(d); - d = x; -} - -template -Q_INLINE_TEMPLATE QHash &QHash::operator=(const QHash &other) -{ - if (d != other.d) { - QHashData *o = other.d; - o->ref.ref(); - if (!d->ref.deref()) - freeData(d); - d = o; - if (!d->sharable) - detach_helper(); - } - return *this; -} - -template -Q_INLINE_TEMPLATE T QHash::value(const Key &akey) const -{ - Node *node; - if (d->size == 0 || (node = *findNode(akey)) == e) { - return T(); - } else { - return node->value; - } -} - -template -Q_INLINE_TEMPLATE T QHash::value(const Key &akey, const T &adefaultValue) const -{ - Node *node; - if (d->size == 0 || (node = *findNode(akey)) == e) { - return adefaultValue; - } else { - return node->value; - } -} - -template -Q_OUTOFLINE_TEMPLATE QList QHash::keys() const -{ - QList res; - res.reserve(size()); - const_iterator i = begin(); - while (i != end()) { - res.append(i.key()); - ++i; - } - return res; -} - -template -Q_OUTOFLINE_TEMPLATE QList QHash::keys(const T &avalue) const -{ - QList res; - const_iterator i = begin(); - while (i != end()) { - if (i.value() == avalue) - res.append(i.key()); - ++i; - } - return res; -} - -template -Q_OUTOFLINE_TEMPLATE Key QHash::key(const T &avalue) const -{ - return key(avalue, Key()); -} - -template -Q_OUTOFLINE_TEMPLATE Key QHash::key(const T &avalue, const Key &defaultValue) const -{ - const_iterator i = begin(); - while (i != end()) { - if (i.value() == avalue) - return i.key(); - ++i; - } - - return defaultValue; -} - -template -Q_OUTOFLINE_TEMPLATE QList QHash::values() const -{ - QList res; - res.reserve(size()); - const_iterator i = begin(); - while (i != end()) { - res.append(i.value()); - ++i; - } - return res; -} - -template -Q_OUTOFLINE_TEMPLATE int QHash::count(const Key &akey) const -{ - int cnt = 0; - Node *node = *findNode(akey); - if (node != e) { - do { - ++cnt; - } while ((node = node->next) != e && node->key == akey); - } - return cnt; -} - -template -Q_INLINE_TEMPLATE const T QHash::operator[](const Key &akey) const -{ - return value(akey); -} - -template -Q_INLINE_TEMPLATE T &QHash::operator[](const Key &akey) -{ - detach(); - - uint h; - Node **node = findNode(akey, &h); - if (*node == e) { - if (d->willGrow()) - node = findNode(akey, h); - return createNode(h, akey, T(), node)->value; - } - return (*node)->value; -} - -template -Q_INLINE_TEMPLATE typename QHash::iterator QHash::insert(const Key &akey, - const T &avalue) -{ - detach(); - - uint h; - Node **node = findNode(akey, &h); - if (*node == e) { - if (d->willGrow()) - node = findNode(akey, h); - return iterator(createNode(h, akey, avalue, node)); - } - - if (!std::is_same::value) - (*node)->value = avalue; - return iterator(*node); -} - -template -Q_INLINE_TEMPLATE void QHash::insert(const QHash &hash) -{ - if (d == hash.d) - return; - - detach(); - - QHashData::Node *i = hash.d->firstNode(); - QHashData::Node *end = reinterpret_cast(hash.e); - while (i != end) { - Node *n = concrete(i); - Node **node = findNode(n->key, n->h); - if (*node == e) { - if (d->willGrow()) - node = findNode(n->key, n->h); - createNode(n->h, n->key, n->value, node); - } else { - if (!std::is_same::value) - (*node)->value = n->value; - } - i = QHashData::nextNode(i); - } -} - -template -Q_OUTOFLINE_TEMPLATE int QHash::remove(const Key &akey) -{ - if (isEmpty()) // prevents detaching shared null - return 0; - detach(); - - int oldSize = d->size; - Node **node = findNode(akey); - if (*node != e) { - bool deleteNext = true; - do { - Node *next = (*node)->next; - deleteNext = (next != e && next->key == (*node)->key); - deleteNode(*node); - *node = next; - --d->size; - } while (deleteNext); - d->hasShrunk(); - } - return oldSize - d->size; -} - -template -Q_OUTOFLINE_TEMPLATE T QHash::take(const Key &akey) -{ - if (isEmpty()) // prevents detaching shared null - return T(); - detach(); - - Node **node = findNode(akey); - if (*node != e) { - T t = std::move((*node)->value); - Node *next = (*node)->next; - deleteNode(*node); - *node = next; - --d->size; - d->hasShrunk(); - return t; - } - return T(); -} - -template -Q_OUTOFLINE_TEMPLATE typename QHash::iterator QHash::erase(const_iterator it) -{ - Q_ASSERT_X(isValidIterator(it), "QHash::erase", "The specified iterator argument 'it' is invalid"); - - if (it == const_iterator(e)) - return iterator(it.i); - - if (d->ref.isShared()) { - // save 'it' across the detach: - int bucketNum = (it.i->h % d->numBuckets); - const_iterator bucketIterator(*(d->buckets + bucketNum)); - int stepsFromBucketStartToIte = 0; - while (bucketIterator != it) { - ++stepsFromBucketStartToIte; - ++bucketIterator; - } - detach(); - it = const_iterator(*(d->buckets + bucketNum)); - while (stepsFromBucketStartToIte > 0) { - --stepsFromBucketStartToIte; - ++it; - } - } - - iterator ret(it.i); - ++ret; - - Node *node = concrete(it.i); - Node **node_ptr = reinterpret_cast(&d->buckets[node->h % d->numBuckets]); - while (*node_ptr != node) - node_ptr = &(*node_ptr)->next; - *node_ptr = node->next; - deleteNode(node); - --d->size; - return ret; -} - -template -Q_INLINE_TEMPLATE void QHash::reserve(int asize) -{ - detach(); - d->rehash(-qMax(asize, 1)); -} - -template -Q_INLINE_TEMPLATE typename QHash::const_iterator QHash::find(const Key &akey) const -{ - return const_iterator(*findNode(akey)); -} - -template -Q_INLINE_TEMPLATE typename QHash::const_iterator QHash::constFind(const Key &akey) const -{ - return const_iterator(*findNode(akey)); -} - -template -Q_INLINE_TEMPLATE typename QHash::iterator QHash::find(const Key &akey) -{ - detach(); - return iterator(*findNode(akey)); -} - -template -Q_INLINE_TEMPLATE bool QHash::contains(const Key &akey) const -{ - return *findNode(akey) != e; -} - -template -Q_OUTOFLINE_TEMPLATE typename QHash::Node **QHash::findNode(const Key &akey, uint h) const -{ - Node **node; - - if (d->numBuckets) { - node = reinterpret_cast(&d->buckets[h % d->numBuckets]); - Q_ASSERT(*node == e || (*node)->next); - while (*node != e && !(*node)->same_key(h, akey)) - node = &(*node)->next; - } else { - node = const_cast(reinterpret_cast(&e)); - } - return node; -} - -template -Q_OUTOFLINE_TEMPLATE typename QHash::Node **QHash::findNode(const Key &akey, - uint *ahp) const -{ - uint h = 0; - - if (d->numBuckets || ahp) { - h = qHash(akey, d->seed); - if (ahp) - *ahp = h; - } - return findNode(akey, h); -} - -template -Q_OUTOFLINE_TEMPLATE bool QHash::operator==(const QHash &other) const -{ - if (d == other.d) - return true; - if (size() != other.size()) - return false; - - const_iterator it = begin(); - - while (it != end()) { - // Build two equal ranges for i.key(); one for *this and one for other. - // For *this we can avoid a lookup via equal_range, as we know the beginning of the range. - auto thisEqualRangeStart = it; - const Key &thisEqualRangeKey = it.key(); - size_type n = 0; - do { - ++it; - ++n; - } while (it != end() && it.key() == thisEqualRangeKey); - - const auto otherEqualRange = other.equal_range(thisEqualRangeKey); - - if (n != std::distance(otherEqualRange.first, otherEqualRange.second)) - return false; - - // Keys in the ranges are equal by construction; this checks only the values. - if (!qt_is_permutation(thisEqualRangeStart, it, otherEqualRange.first, otherEqualRange.second)) - return false; - } - - return true; -} - -template -QPair::iterator, typename QHash::iterator> QHash::equal_range(const Key &akey) -{ - detach(); - auto pair = qAsConst(*this).equal_range(akey); - return qMakePair(iterator(pair.first.i), iterator(pair.second.i)); -} - -template -QPair::const_iterator, typename QHash::const_iterator> QHash::equal_range(const Key &akey) const noexcept -{ - Node *node = *findNode(akey); - const_iterator firstIt = const_iterator(node); - - if (node != e) { - // equal keys must hash to the same value and so they all - // end up in the same bucket. So we can use node->next, - // which only works within a bucket, instead of (out-of-line) - // QHashData::nextNode() - while (node->next != e && node->next->key == akey) - node = node->next; - - // 'node' may be the last node in the bucket. To produce the end iterator, we'd - // need to enter the next bucket in this case, so we need to use - // QHashData::nextNode() here, which, unlike node->next above, can move between - // buckets. - node = concrete(QHashData::nextNode(reinterpret_cast(node))); - } - - return qMakePair(firstIt, const_iterator(node)); -} - -template -class QMultiHash : public QHash -{ public: - QMultiHash() noexcept {} + using key_type = Key; + using mapped_type = T; + using value_type = T; + using size_type = qsizetype; + using difference_type = qsizetype; + using reference = T &; + using const_reference = const T &; + + QMultiHash() noexcept = default; inline QMultiHash(std::initializer_list > list) + : d(new Data(list.size())) { - this->reserve(int(list.size())); for (typename std::initializer_list >::const_iterator it = list.begin(); it != list.end(); ++it) insert(it->first, it->second); } @@ -1028,293 +1222,681 @@ public: insert(f->first, f->second); } #endif - // compiler-generated copy/move ctors/assignment operators are fine! - // compiler-generated destructor is fine! + QMultiHash(const QMultiHash &other) noexcept + : d(other.d), m_size(other.m_size) + { + if (d) + d->ref.ref(); + } + ~QMultiHash() + { + if (d && !d->ref.deref()) + delete d; + } - QMultiHash(const QHash &other) : QHash(other) {} - QMultiHash(QHash &&other) noexcept : QHash(std::move(other)) {} - void swap(QMultiHash &other) noexcept { QHash::swap(other); } // prevent QMultiHash<->QHash swaps + QMultiHash &operator=(const QMultiHash &other) noexcept(std::is_nothrow_destructible::value) + { + if (d != other.d) { + Data *o = other.d; + if (o) + o->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = o; + m_size = other.m_size; + } + return *this; + } + QMultiHash(QMultiHash &&other) noexcept : d(other.d), m_size(other.m_size) + { + other.d = nullptr; + other.m_size = 0; + } + QMultiHash &operator=(QMultiHash &&other) noexcept(std::is_nothrow_destructible::value) + { + QMultiHash moved(std::move(other)); + swap(moved); + return *this; + } - inline typename QHash::iterator replace(const Key &key, const T &value) - { return QHash::insert(key, value); } + QMultiHash(const QHash &other) + : QMultiHash(other.begin(), other.end()) + {} + void swap(QMultiHash &other) noexcept { qSwap(d, other.d); qSwap(m_size, other.m_size); } - typename QHash::iterator insert(const Key &key, const T &value); + bool operator==(const QMultiHash &other) const noexcept + { + if (d == other.d) + return true; + if (!d || ! other.d) + return false; + if (m_size != other.m_size || d->size != other.d->size) + return false; + for (auto it = other.d->begin(); it != other.d->end(); ++it) { + auto i = d->find(it.node()->key); + if (i == d->end()) + return false; + Chain *e = it.node()->value; + while (e) { + Chain *oe = i.node()->value; + while (oe) { + if (oe->value == e->value) + break; + oe = oe->next; + } + if (!oe) + return false; + e = e->next; + } + } + // all values must be the same as size is the same + return true; + } + bool operator!=(const QMultiHash &other) const noexcept { return !(*this == other); } - inline QMultiHash &unite(const QMultiHash &other); + inline qsizetype size() const noexcept { return m_size; } + + inline bool isEmpty() const noexcept { return !m_size; } + + inline qsizetype capacity() const noexcept { return d ? qsizetype(d->numBuckets >> 1) : 0; } + void reserve(qsizetype size) + { + if (isDetached()) + d->rehash(size); + else + d = Data::detached(d, size_t(size)); + } + inline void squeeze() { reserve(0); } + + inline void detach() { if (!d || d->ref.isShared()) d = Data::detached(d); } + inline bool isDetached() const noexcept { return d && !d->ref.isShared(); } + bool isSharedWith(const QMultiHash &other) const noexcept { return d == other.d; } + + void clear() noexcept(std::is_nothrow_destructible::value) + { + if (d && !d->ref.deref()) + delete d; + d = nullptr; + m_size = 0; + } + + qsizetype remove(const Key &key) + { + if (isEmpty()) // prevents detaching shared null + return 0; + detach(); + + auto it = d->find(key); + if (it.isUnused()) + return 0; + qsizetype n = Node::freeChain(it.node()); + m_size -= n; + Q_ASSERT(m_size >= 0); + d->erase(it); + return n; + } + T take(const Key &key) + { + if (isEmpty()) // prevents detaching shared null + return T(); + detach(); + + auto it = d->find(key); + if (it.isUnused()) + return T(); + Q_ASSERT(it.node()->value.size()); + Chain *e = it.node()->value; + Q_ASSERT(e); + if (!e->next) + d->erase(it); + else + it.node()->value = e->next; + --m_size; + Q_ASSERT(m_size >= 0); + T t = std::move(e->value); + delete e; + return t; + } + + bool contains(const Key &key) const noexcept + { + if (!d) + return false; + return d->findNode(key) != nullptr; + } + + Key key(const T &value, const Key &defaultKey = Key()) const noexcept + { + if (d) { + auto i = d->begin(); + while (i != d->end()) { + Chain *e = i.node()->value; + if (e->contains(value)) + return i.node()->key; + ++i; + } + } + + return defaultKey; + } + T value(const Key &key, const T &defaultValue = T()) const noexcept + { + if (d) { + Node *n = d->findNode(key); + if (n) { + Q_ASSERT(n->value); + return n->value->value; + } + } + return defaultValue; + } + + T &operator[](const Key &key) + { + detach(); + Node *n = d->findAndInsertNode(key); + Q_ASSERT(n); + return n->value->value; + } + + const T operator[](const Key &key) const noexcept + { + return value(key); + } + + QVector uniqueKeys() const + { + QVector res; + if (d) { + auto i = d->begin(); + while (i != d->end()) { + res.append(i.node()->key); + ++i; + } + } + return res; + } + + QVector keys() const + { + return QVector(keyBegin(), keyEnd()); + } + QVector keys(const T &value) const + { + QVector res; + const_iterator i = begin(); + while (i != end()) { + if (i.value()->contains(value)) + res.append(i.key()); + ++i; + } + return res; + } + QVector values() const + { + return QVector(begin(), end()); + } + QVector values(const Key &key) const + { + QVector values; + if (d) { + Node *n = d->findNode(key); + if (n) { + Chain *e = n->value; + while (e) { + values.append(e->value); + e = e->next; + } + } + } + return values; + } + + class const_iterator; + + class iterator + { + using piter = typename QHashPrivate::iterator; + friend class const_iterator; + friend class QMultiHash; + piter i; + Chain **e = nullptr; + explicit inline iterator(piter it, Chain **entry = nullptr) noexcept : i(it), e(entry) + { + if (!it.atEnd() && !e) { + e = &it.node()->value; + Q_ASSERT(e && *e); + } + } + + public: + typedef std::forward_iterator_tag iterator_category; + typedef qptrdiff difference_type; + typedef T value_type; + typedef T *pointer; + typedef T &reference; + + constexpr iterator() noexcept = default; + + inline const Key &key() const noexcept { return i.node()->key; } + inline T &value() const noexcept { return (*e)->value; } + inline T &operator*() const noexcept { return (*e)->value; } + inline T *operator->() const noexcept { return &(*e)->value; } + inline bool operator==(const iterator &o) const noexcept { return e == o.e; } + inline bool operator!=(const iterator &o) const noexcept { return e != o.e; } + + inline iterator &operator++() noexcept { + Q_ASSERT(e && *e); + e = &(*e)->next; + Q_ASSERT(e); + if (!*e) { + ++i; + e = i.atEnd() ? nullptr : &i.node()->value; + } + return *this; + } + inline iterator operator++(int) noexcept { + iterator r = *this; + ++(*this); + return r; + } + + inline bool operator==(const const_iterator &o) const noexcept { return e == o.e; } + inline bool operator!=(const const_iterator &o) const noexcept { return e != o.e; } + }; + friend class iterator; + + class const_iterator + { + using piter = typename QHashPrivate::iterator; + friend class iterator; + friend class QMultiHash; + piter i; + Chain **e = nullptr; + explicit inline const_iterator(piter it, Chain **entry = nullptr) noexcept : i(it), e(entry) + { + if (!it.atEnd() && !e) { + e = &it.node()->value; + Q_ASSERT(e && *e); + } + } + + public: + typedef std::forward_iterator_tag iterator_category; + typedef qptrdiff difference_type; + typedef T value_type; + typedef const T *pointer; + typedef const T &reference; + + constexpr const_iterator() noexcept = default; + inline const_iterator(const iterator &o) noexcept : i(o.i), e(o.e) { } + + inline const Key &key() const noexcept { return i.node()->key; } + inline T &value() const noexcept { return (*e)->value; } + inline T &operator*() const noexcept { return (*e)->value; } + inline T *operator->() const noexcept { return &(*e)->value; } + inline bool operator==(const const_iterator &o) const noexcept { return e == o.e; } + inline bool operator!=(const const_iterator &o) const noexcept { return e != o.e; } + + inline const_iterator &operator++() noexcept { + Q_ASSERT(e && *e); + e = &(*e)->next; + Q_ASSERT(e); + if (!*e) { + ++i; + e = i.atEnd() ? nullptr : &i.node()->value; + } + return *this; + } + inline const_iterator operator++(int) noexcept + { + const_iterator r = *this; + ++(*this); + return r; + } + }; + friend class const_iterator; + + class key_iterator + { + const_iterator i; + + public: + typedef typename const_iterator::iterator_category iterator_category; + typedef qptrdiff difference_type; + typedef Key value_type; + typedef const Key *pointer; + typedef const Key &reference; + + key_iterator() noexcept = default; + explicit key_iterator(const_iterator o) noexcept : i(o) { } + + const Key &operator*() const noexcept { return i.key(); } + const Key *operator->() const noexcept { return &i.key(); } + bool operator==(key_iterator o) const noexcept { return i == o.i; } + bool operator!=(key_iterator o) const noexcept { return i != o.i; } + + inline key_iterator &operator++() noexcept { ++i; return *this; } + inline key_iterator operator++(int) noexcept { return key_iterator(i++);} + const_iterator base() const noexcept { return i; } + }; + + typedef QKeyValueIterator const_key_value_iterator; + typedef QKeyValueIterator key_value_iterator; + + // STL style + inline iterator begin() { detach(); return iterator(d->begin()); } + inline const_iterator begin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline const_iterator cbegin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline const_iterator constBegin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline iterator end() noexcept { return iterator(); } + inline const_iterator end() const noexcept { return const_iterator(); } + inline const_iterator cend() const noexcept { return const_iterator(); } + inline const_iterator constEnd() const noexcept { return const_iterator(); } + inline key_iterator keyBegin() const noexcept { return key_iterator(begin()); } + inline key_iterator keyEnd() const noexcept { return key_iterator(end()); } + inline key_value_iterator keyValueBegin() noexcept { return key_value_iterator(begin()); } + inline key_value_iterator keyValueEnd() noexcept { return key_value_iterator(end()); } + inline const_key_value_iterator keyValueBegin() const noexcept { return const_key_value_iterator(begin()); } + inline const_key_value_iterator constKeyValueBegin() const noexcept { return const_key_value_iterator(begin()); } + inline const_key_value_iterator keyValueEnd() const noexcept { return const_key_value_iterator(end()); } + inline const_key_value_iterator constKeyValueEnd() const noexcept { return const_key_value_iterator(end()); } + + iterator detach(const_iterator it) + { + auto i = it.i; + Chain **e = it.e; + if (d->ref.isShared()) { + // need to store iterator position before detaching + qsizetype n = 0; + Chain *entry = i.node()->value; + while (entry != *it.e) { + ++n; + entry = entry->next; + } + Q_ASSERT(entry); + detach_helper(); + + i = d->detachedIterator(i); + e = &i.node()->value; + while (n) { + e = &(*e)->next; + --n; + } + Q_ASSERT(e && *e); + } + return iterator(i, e); + } + + iterator erase(const_iterator it) + { + Q_ASSERT(d); + iterator i = detach(it); + Chain *e = *i.e; + Chain *next = e->next; + *i.e = next; + delete e; + if (!next) { + if (i.e == &i.i.node()->value) { + // last remaining entry, erase + i = iterator(d->erase(i.i)); + } else { + i = iterator(++it.i); + } + } + --m_size; + Q_ASSERT(m_size >= 0); + return i; + } + + // more Qt + typedef iterator Iterator; + typedef const_iterator ConstIterator; + inline qsizetype count() const noexcept { return size(); } + iterator find(const Key &key) + { + if (isEmpty()) + return end(); + detach(); + auto it = d->find(key); + if (it.isUnused()) + it = d->end(); + return iterator(it); + } + const_iterator find(const Key &key) const noexcept + { + return constFind(key); + } + const_iterator constFind(const Key &key) const noexcept + { + if (isEmpty()) + return end(); + auto it = d->find(key); + if (it.isUnused()) + it = d->end(); + return const_iterator(it); + } + iterator insert(const Key &key, const T &value) + { + detach(); + auto it = d->insertMulti(key, value); + ++m_size; + return iterator(it); + } + + float load_factor() const noexcept { return d ? d->loadFactor() : 0; } + static float max_load_factor() noexcept { return 0.5; } + size_t bucket_count() const noexcept { return d ? d->numBuckets : 0; } + static size_t max_bucket_count() noexcept { return QHashPrivate::GrowthPolicy::maxNumBuckets(); } + + inline bool empty() const noexcept { return isEmpty(); } + + inline typename QMultiHash::iterator replace(const Key &key, const T &value) + { + detach(); + qsizetype s = d->size; + auto it = d->insert(key, value); + m_size += d->size - s; + return iterator(it); + } inline QMultiHash &operator+=(const QMultiHash &other) - { return unite(other); } + { this->unite(other); return *this; } inline QMultiHash operator+(const QMultiHash &other) const { QMultiHash result = *this; result += other; return result; } - using QHash::contains; - using QHash::remove; - using QHash::count; - using QHash::find; - using QHash::constFind; - using QHash::values; - using QHash::findNode; - using QHash::createNode; - using QHash::concrete; - using QHash::detach; - - using typename QHash::Node; - using typename QHash::iterator; - using typename QHash::const_iterator; - - bool contains(const Key &key, const T &value) const; - - int remove(const Key &key, const T &value); - - int count(const Key &key, const T &value) const; - - QList uniqueKeys() const; - - QList values(const Key &akey) const; - - typename QHash::iterator find(const Key &key, const T &value) { - typename QHash::iterator i(find(key)); - typename QHash::iterator end(this->end()); - while (i != end && i.key() == key) { - if (i.value() == value) - return i; - ++i; - } - return end; + bool contains(const Key &key, const T &value) const noexcept + { + if (isEmpty()) + return false; + auto n = d->findNode(key); + if (n == nullptr) + return false; + return n->value->contains(value); } - typename QHash::const_iterator find(const Key &key, const T &value) const { - typename QHash::const_iterator i(constFind(key)); - typename QHash::const_iterator end(QHash::constEnd()); - while (i != end && i.key() == key) { - if (i.value() == value) - return i; - ++i; - } - return end; - } - typename QHash::const_iterator constFind(const Key &key, const T &value) const - { return find(key, value); } -private: - T &operator[](const Key &key); - const T operator[](const Key &key) const; -}; -template -Q_INLINE_TEMPLATE typename QHash::iterator QMultiHash::insert(const Key &akey, const T &avalue) -{ - detach(); - this->d->willGrow(); + qsizetype remove(const Key &key, const T &value) + { + if (isEmpty()) // prevents detaching shared null + return false; + detach(); - uint h; - Node **nextNode = findNode(akey, &h); - return iterator(createNode(h, akey, avalue, nextNode)); -} - -template -Q_INLINE_TEMPLATE QMultiHash &QMultiHash::unite(const QMultiHash &other) -{ - if (this->d == &QHashData::shared_null) { - *this = other; - } else { -#if QT_DEPRECATED_SINCE(5, 15) - QMultiHash copy(other); - const_iterator it = copy.constEnd(); - while (it != copy.constBegin()) { - it.i = QHashData::previousNode(it.i); - insert(it.key(), it.value()); - } -#else - const QMultiHash copy(other); - const_iterator it = copy.cbegin(); - const const_iterator end = copy.cend(); - while (it != end) { - const auto rangeStart = it++; - while (it != end && rangeStart.key() == it.key()) - ++it; - const qint64 last = std::distance(rangeStart, it) - 1; - for (qint64 i = last; i >= 0; --i) { - auto next = std::next(rangeStart, i); - insert(next.key(), next.value()); + auto it = d->find(key); + if (it.isUnused()) + return 0; + qsizetype n = 0; + Chain **e = &it.node()->value; + while (*e) { + Chain *entry = *e; + if (entry->value == value) { + *e = entry->next; + delete entry; + ++n; + } else { + e = &entry->next; } } -#endif + if (!it.node()->value) + d->erase(it); + m_size -= n; + Q_ASSERT(m_size >= 0); + return n; } - return *this; -} - -template -Q_INLINE_TEMPLATE bool QMultiHash::contains(const Key &key, const T &value) const -{ - return constFind(key, value) != QHash::constEnd(); -} - -template -Q_INLINE_TEMPLATE int QMultiHash::remove(const Key &key, const T &value) -{ - int n = 0; - typename QHash::iterator i(find(key)); - typename QHash::iterator end(QHash::end()); - while (i != end && i.key() == key) { - if (i.value() == value) { - i = this->erase(i); + qsizetype count(const Key &key) const noexcept + { + auto it = d->find(key); + if (it.isUnused()) + return 0; + qsizetype n = 0; + Chain *e = it.node()->value; + while (e) { ++n; - } else { + e = e->next; + } + + return n; + } + + qsizetype count(const Key &key, const T &value) const noexcept + { + auto it = d->find(key); + if (it.isUnused()) + return 0; + qsizetype n = 0; + Chain *e = it.node()->value; + while (e) { + if (e->value == value) + ++n; + e = e->next; + } + + return n; + } + + iterator find(const Key &key, const T &value) + { + detach(); + auto it = constFind(key, value); + return iterator(it.i, it.e); + } + const_iterator find(const Key &key, const T &value) const noexcept + { + return constFind(key, value); + } + const_iterator constFind(const Key &key, const T &value) const noexcept + { + const_iterator i(constFind(key)); + const_iterator end(constEnd()); + while (i != end && i.key() == key) { + if (i.value() == value) + return i; ++i; } + return end; } - return n; -} -template -Q_INLINE_TEMPLATE int QMultiHash::count(const Key &key, const T &value) const -{ - int n = 0; - typename QHash::const_iterator i(constFind(key)); - typename QHash::const_iterator end(QHash::constEnd()); - while (i != end && i.key() == key) { - if (i.value() == value) - ++n; - ++i; - } - return n; -} - -template -Q_OUTOFLINE_TEMPLATE QList QMultiHash::uniqueKeys() const -{ - QList res; - res.reserve(QHash::size()); // May be too much, but assume short lifetime - typename QHash::const_iterator i = QHash::begin(); - if (i != QHash::end()) { - for (;;) { - const Key &aKey = i.key(); - res.append(aKey); - do { - if (++i == QHash::end()) - goto break_out_of_outer_loop; - } while (aKey == i.key()); + QMultiHash &unite(const QMultiHash &other) + { + if (isEmpty()) { + *this = other; + } else if (other.isEmpty()) { + ; + } else { + QMultiHash copy(other); + detach(); + for (auto cit = copy.cbegin(); cit != copy.cend(); ++cit) + insert(cit.key(), *cit); } + return *this; } -break_out_of_outer_loop: - return res; -} -#if QT_DEPRECATED_SINCE(5, 15) - -template -Q_OUTOFLINE_TEMPLATE typename QHash::iterator QHash::insertMulti(const Key &key, const T &value) { - return static_cast *>(this)->insert(key, value); -} - -template -Q_OUTOFLINE_TEMPLATE QHash &QHash::unite(const QHash &other) { - return static_cast *>(this)->unite(other); -} - -template -Q_OUTOFLINE_TEMPLATE QList QHash::values(const Key &akey) const -{ - return static_cast *>(this)->values(akey); -} - -template -Q_OUTOFLINE_TEMPLATE QList QHash::uniqueKeys() const -{ - return static_cast *>(this)->uniqueKeys(); -} -#endif - -template -Q_OUTOFLINE_TEMPLATE QList QMultiHash::values(const Key &akey) const -{ - QList res; - Node *node = *findNode(akey); - if (node != this->e) { - do { - res.append(node->value); - } while ((node = node->next) != this->e && node->key == akey); + QPair equal_range(const Key &key) + { + detach(); + auto pair = qAsConst(*this).equal_range(key); + return qMakePair(iterator(pair.first.i), iterator(pair.second.i)); } - return res; -} + + QPair equal_range(const Key &key) const noexcept + { + auto it = d->find(key); + if (it.isUnused()) + return qMakePair(end(), end()); + auto end = it; + ++end; + return qMakePair(const_iterator(it), const_iterator(end)); + } + +private: + void detach_helper() + { + if (!d) { + d = new Data; + return; + } + Data *dd = new Data(*d); + if (!d->ref.deref()) + delete d; + d = dd; + } +}; #if !defined(QT_NO_JAVA_STYLE_ITERATORS) -template +template class QHashIterator { typedef typename QHash::const_iterator const_iterator; typedef const_iterator Item; QHash c; const_iterator i, n; - inline bool item_exists() const { return n != c.constEnd(); } + inline bool item_exists() const noexcept { return n != c.constEnd(); } public: - inline QHashIterator(const QHash &container) + inline QHashIterator(const QHash &container) noexcept : c(container), i(c.constBegin()), n(c.constEnd()) - { - } - inline QHashIterator &operator=(const QHash &container) + { } + inline QHashIterator &operator=(const QHash &container) noexcept { c = container; i = c.constBegin(); n = c.constEnd(); return *this; } - inline void toFront() + inline void toFront() noexcept { i = c.constBegin(); n = c.constEnd(); } - inline void toBack() + inline void toBack() noexcept { i = c.constEnd(); n = c.constEnd(); } - inline bool hasNext() const { return i != c.constEnd(); } - inline Item next() + inline bool hasNext() const noexcept { return i != c.constEnd(); } + inline Item next() noexcept { n = i++; return n; } - inline Item peekNext() const { return i; } - inline const T &value() const + inline Item peekNext() const noexcept { return i; } + inline const T &value() const noexcept { Q_ASSERT(item_exists()); return *n; } - inline const Key &key() const + inline const Key &key() const noexcept { Q_ASSERT(item_exists()); return n.key(); } - inline bool findNext(const T &t) + inline bool findNext(const T &t) noexcept { while ((n = i) != c.constEnd()) if (*i++ == t) return true; return false; } -#if QT_DEPRECATED_SINCE(5, 15) - inline QT_DEPRECATED_VERSION_5_15 bool hasPrevious() const { return i != c.constBegin(); } - inline QT_DEPRECATED_VERSION_5_15 Item previous() - { - n = --i; - return n; - } - inline QT_DEPRECATED_VERSION_5_15 Item peekPrevious() const - { - const_iterator p = i; - return --p; - } - inline bool QT_DEPRECATED_VERSION_5_15 findPrevious(const T &t) - { - while (i != c.constBegin()) - if (*(n = --i) == t) - return true; - n = c.constEnd(); - return false; - } -#endif }; template @@ -1325,10 +1907,11 @@ class QMutableHashIterator typedef iterator Item; QHash *c; iterator i, n; - inline bool item_exists() const { return const_iterator(n) != c->constEnd(); } + inline bool item_exists() const noexcept { return const_iterator(n) != c->constEnd(); } public: - inline QMutableHashIterator(QHash &container) : c(&container) + inline QMutableHashIterator(QHash &container) + : c(&container) { i = c->begin(); n = c->end(); @@ -1345,18 +1928,18 @@ public: i = c->begin(); n = c->end(); } - inline void toBack() + inline void toBack() noexcept { i = c->end(); n = c->end(); } - inline bool hasNext() const { return const_iterator(i) != c->constEnd(); } - inline Item next() + inline bool hasNext() const noexcept { return const_iterator(i) != c->constEnd(); } + inline Item next() noexcept { n = i++; return n; } - inline Item peekNext() const { return i; } + inline Item peekNext() const noexcept { return i; } inline void remove() { if (const_iterator(n) != c->constEnd()) { @@ -1369,49 +1952,28 @@ public: if (const_iterator(n) != c->constEnd()) *n = t; } - inline T &value() + inline T &value() noexcept { Q_ASSERT(item_exists()); return *n; } - inline const T &value() const + inline const T &value() const noexcept { Q_ASSERT(item_exists()); return *n; } - inline const Key &key() const + inline const Key &key() const noexcept { Q_ASSERT(item_exists()); return n.key(); } - inline bool findNext(const T &t) + inline bool findNext(const T &t) noexcept { while (const_iterator(n = i) != c->constEnd()) if (*i++ == t) return true; return false; } -#if QT_DEPRECATED_SINCE(5, 15) - inline QT_DEPRECATED_VERSION_5_15 bool hasPrevious() const { return const_iterator(i) != c->constBegin(); } - inline QT_DEPRECATED_VERSION_5_15 Item previous() - { - n = --i; - return n; - } - inline QT_DEPRECATED_VERSION_5_15 Item peekPrevious() const - { - iterator p = i; - return --p; - } - inline QT_DEPRECATED_VERSION_5_15 bool findPrevious(const T &t) - { - while (const_iterator(i) != c->constBegin()) - if (*(n = --i) == t) - return true; - n = c->end(); - return false; - } -#endif }; #endif // !QT_NO_JAVA_STYLE_ITERATORS @@ -1438,8 +2000,4 @@ inline uint qHash(const QMultiHash &key, uint seed = 0) QT_END_NAMESPACE -#if defined(Q_CC_MSVC) -#pragma warning( pop ) -#endif - #endif // QHASH_H diff --git a/src/corelib/tools/qset.h b/src/corelib/tools/qset.h index ef363ed52d..5b7aa8fa28 100644 --- a/src/corelib/tools/qset.h +++ b/src/corelib/tools/qset.h @@ -281,24 +281,14 @@ Q_INLINE_TEMPLATE bool QSet::intersects(const QSet &other) const const bool otherIsBigger = other.size() > size(); const QSet &smallestSet = otherIsBigger ? *this : other; const QSet &biggestSet = otherIsBigger ? other : *this; - const bool equalSeeds = q_hash.d->seed == other.q_hash.d->seed; typename QSet::const_iterator i = smallestSet.cbegin(); typename QSet::const_iterator e = smallestSet.cend(); - if (Q_LIKELY(equalSeeds)) { - // If seeds are equal we take the fast path so no hash is recalculated. - while (i != e) { - if (*biggestSet.q_hash.findNode(*i, i.i.i->h) != biggestSet.q_hash.e) - return true; - ++i; - } - } else { - while (i != e) { - if (biggestSet.contains(*i)) - return true; - ++i; - } - } + while (i != e) { + if (biggestSet.contains(*i)) + return true; + ++i; + } return false; } diff --git a/src/corelib/tools/qsharedpointer.cpp b/src/corelib/tools/qsharedpointer.cpp index 0576fb2bd0..499546da4b 100644 --- a/src/corelib/tools/qsharedpointer.cpp +++ b/src/corelib/tools/qsharedpointer.cpp @@ -1630,7 +1630,7 @@ void QtSharedPointer::internalSafetyCheckCleanCheck() qFatal("Internal consistency error: the number of pointers is not equal!"); if (Q_UNLIKELY(!kp->dPointers.isEmpty())) - qFatal("Pointer cleaning failed: %d entries remaining", kp->dPointers.size()); + qFatal("Pointer cleaning failed: %d entries remaining", int(kp->dPointers.size())); # endif } diff --git a/tests/auto/corelib/tools/collections/tst_collections.cpp b/tests/auto/corelib/tools/collections/tst_collections.cpp index 6fcf726c21..456c3e6cf9 100644 --- a/tests/auto/corelib/tools/collections/tst_collections.cpp +++ b/tests/auto/corelib/tools/collections/tst_collections.cpp @@ -1286,19 +1286,13 @@ void tst_Collections::hash() QHash hash; for (int i = 0; i < 1000; ++i) hash.insert(i, i); - QVERIFY(hash.capacity() == 1031); - hash.squeeze(); - QVERIFY(hash.capacity() == 521); - - hash.insert(12345, 12345); - QVERIFY(hash.capacity() == 1031); + QVERIFY(hash.capacity() > 1000); for (int j = 0; j < 900; ++j) hash.remove(j); - QVERIFY(hash.capacity() == 257); + QVERIFY(hash.capacity() > 1000); hash.squeeze(); - QVERIFY(hash.capacity() == 67); - hash.reserve(0); + QVERIFY(hash.capacity() < 200); } } @@ -1331,13 +1325,21 @@ void tst_Collections::hash() hash1.unite(hash2); QCOMPARE(hash1.size(), 5); - QCOMPARE(hash1.values(), - (QList() << "Gamma" << "Gamma" << "Beta" << "Gamma" << "Alpha")); + auto values = hash1.values(); + qSort(values); + QList expected; + expected << "Gamma" << "Gamma" << "Beta" << "Gamma" << "Alpha"; + qSort(expected); + QCOMPARE(values, expected); hash2 = hash1; hash2.unite(hash2); QCOMPARE(hash2.size(), 10); - QCOMPARE(hash2.values(), hash1.values() + hash1.values()); + values = hash2.values(); + qSort(values); + expected += expected; + qSort(expected); + QCOMPARE(values, expected); } } diff --git a/tests/auto/corelib/tools/qhash/tst_qhash.cpp b/tests/auto/corelib/tools/qhash/tst_qhash.cpp index 2a18f8d3e6..b987adaa3f 100644 --- a/tests/auto/corelib/tools/qhash/tst_qhash.cpp +++ b/tests/auto/corelib/tools/qhash/tst_qhash.cpp @@ -62,7 +62,6 @@ private slots: void keyIterator(); void keyValueIterator(); void keys_values_uniqueKeys(); // slightly modified from tst_QMap - void noNeedlessRehashes(); void const_shared_null(); void twoArguments_qHash(); @@ -70,6 +69,8 @@ private slots: void eraseValidIteratorOnSharedHash(); void equal_range(); void insert_hash(); + + void badHashFunction(); }; struct IdentityTracker { @@ -1325,20 +1326,6 @@ void tst_QHash::keys_values_uniqueKeys() QVERIFY(sorted(hash.values()) == sorted(QList() << 2 << 1 << 4 << -2)); } -void tst_QHash::noNeedlessRehashes() -{ - QHash hash; - for (int i = 0; i < 512; ++i) { - int j = (i * 345) % 512; - hash.insert(j, j); - int oldCapacity = hash.capacity(); - hash[j] = j + 1; - QCOMPARE(oldCapacity, hash.capacity()); - hash.insert(j, j + 1); - QCOMPARE(oldCapacity, hash.capacity()); - } -} - void tst_QHash::const_shared_null() { QHash hash2; @@ -1663,5 +1650,33 @@ void tst_QHash::insert_hash() } } +struct BadKey { + int k; + BadKey(int i) : k(i) {} + bool operator==(const BadKey &other) const + { + return k == other.k; + } +}; + +size_t qHash(BadKey, size_t seed) +{ + return seed; +} + +void tst_QHash::badHashFunction() +{ + QHash hash; + for (int i = 0; i < 10000; ++i) + hash.insert(i, i); + + for (int i = 0; i < 10000; ++i) + QCOMPARE(hash.value(i), i); + + for (int i = 10000; i < 20000; ++i) + QVERIFY(!hash.contains(i)); + +} + QTEST_APPLESS_MAIN(tst_QHash) #include "tst_qhash.moc" diff --git a/tests/auto/corelib/tools/qset/tst_qset.cpp b/tests/auto/corelib/tools/qset/tst_qset.cpp index 0ca8be7fa3..21ceee87c0 100644 --- a/tests/auto/corelib/tools/qset/tst_qset.cpp +++ b/tests/auto/corelib/tools/qset/tst_qset.cpp @@ -247,21 +247,24 @@ void tst_QSet::squeeze() set.squeeze(); QVERIFY(set.capacity() < 100); - for (int i = 0; i < 500; ++i) + for (int i = 0; i < 512; ++i) set.insert(i); - QVERIFY(set.capacity() >= 500 && set.capacity() < 10000); + QVERIFY(set.capacity() == 512); set.reserve(50000); QVERIFY(set.capacity() >= 50000); set.squeeze(); - QVERIFY(set.capacity() < 500); + QVERIFY(set.capacity() == 512); set.remove(499); - QVERIFY(set.capacity() < 500); + QVERIFY(set.capacity() == 512); set.insert(499); - QVERIFY(set.capacity() >= 500); + QVERIFY(set.capacity() == 512); + + set.insert(1000); + QVERIFY(set.capacity() == 1024); for (int i = 0; i < 500; ++i) set.remove(i); @@ -495,13 +498,13 @@ void tst_QSet::end() QVERIFY(i == j); QVERIFY(k == ell); - QVERIFY(i != k); - QVERIFY(j != ell); QVERIFY(set1.constBegin() != set1.constEnd()); QVERIFY(set2.constBegin() == set2.constEnd()); + QVERIFY(set1.constBegin() != set2.constBegin()); } + set2 = set1; {