1 // Copyright 2021 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef SRC_GN_POINTER_SET_H_ 6 #define SRC_GN_POINTER_SET_H_ 7 8 #include <functional> 9 #include <vector> 10 11 #include "gn/hash_table_base.h" 12 13 // PointerSet<T> is a fast implemention of a set of non-owning and non-null 14 // typed pointer values (of type T*). 15 // 16 // Note that this intentionally does not support a find() method 17 // for performance reasons, instead callers must use contains(), add() 18 // or erase() directly to perform lookups or conditional insertion/removal. 19 // 20 // Only constant iterators are provided. Moreover, the slightly faster 21 // iteration form can be used for performance as in: 22 // 23 // for (auto it = my_set.begin(); it.valid(); ++it) { 24 // T* ptr = *it; 25 // ... 26 // } 27 // 28 // Instead of: 29 // 30 // for (T* ptr : my_set) { 31 // ... 32 // } 33 // 34 // The PointerSetNode type implements the methods required by HashTableBase<> 35 // to store and hash a pointer directly in the buckets array. The special 36 // address 1 is used as the tombstone value to support removal. 37 // 38 // Null nodes are marked with an empty pointer, which means that nullptr 39 // itself cannot be stored in the set. 40 struct PointerSetNode { 41 const void* ptr_; is_nullPointerSetNode42 bool is_null() const { return !ptr_; } is_tombstonePointerSetNode43 bool is_tombstone() const { return ptr_ == MakeTombstone(); } is_validPointerSetNode44 bool is_valid() const { return !is_null() && !is_tombstone(); } hash_valuePointerSetNode45 size_t hash_value() const { return MakeHash(ptr_); } 46 47 // Return the tomebstone value. This could be a static constexpr value 48 // if C++ didn't complain about reinterpret_cast<> here. MakeTombstonePointerSetNode49 static const void* MakeTombstone() { 50 return reinterpret_cast<const void*>(1u); 51 } 52 53 // Return the hash corresponding to a given pointer. MakeHashPointerSetNode54 static size_t MakeHash(const void* ptr) { 55 return std::hash<const void*>()(ptr); 56 } 57 }; 58 59 template <typename T> 60 class PointerSet : public HashTableBase<PointerSetNode> { 61 public: 62 using NodeType = PointerSetNode; 63 using BaseType = HashTableBase<NodeType>; 64 65 PointerSet() = default; 66 67 // Allow copying pointer sets. PointerSet(const PointerSet & other)68 PointerSet(const PointerSet& other) : BaseType() { insert(other); } 69 PointerSet& operator=(const PointerSet& other) { 70 if (this != &other) { 71 this->~PointerSet(); 72 new (this) PointerSet(other); 73 } 74 return *this; 75 } 76 77 // Redefine move operators since the copy constructor above 78 // masks the base ones by default. PointerSet(PointerSet && other)79 PointerSet(PointerSet&& other) noexcept : BaseType(std::move(other)) {} 80 PointerSet& operator=(PointerSet&& other) noexcept { 81 if (this != &other) { 82 this->~PointerSet(); 83 new (this) PointerSet(std::move(other)); 84 } 85 return *this; 86 } 87 88 // Range constructor. 89 template <typename InputIter> PointerSet(InputIter first,InputIter last)90 PointerSet(InputIter first, InputIter last) { 91 for (; first != last; ++first) 92 add(*first); 93 } 94 95 // Clear the set. clear()96 void clear() { NodeClear(); } 97 98 // Add one pointer to the set. Return true if the pointer was 99 // added, or false if its was already in the set. add(T * ptr)100 bool add(T* ptr) { 101 NodeType* node = Lookup(ptr); 102 if (node->is_valid()) 103 return false; 104 105 node->ptr_ = ptr; 106 UpdateAfterInsert(); 107 return true; 108 } 109 110 // Return true if a given pointer is already in the set. contains(T * ptr)111 bool contains(T* ptr) const { return Lookup(ptr)->is_valid(); } 112 113 // Try to remove a pointer from the set. Return true in case of 114 // removal, or false if the pointer was not in the set. erase(T * ptr)115 bool erase(T* ptr) { 116 NodeType* node = Lookup(ptr); 117 if (!node->is_valid()) 118 return false; 119 node->ptr_ = node->MakeTombstone(); 120 UpdateAfterRemoval(); 121 return true; 122 } 123 124 // Same as add() but does not return a boolean. 125 // This minimizes code changes when PointerSet<> replaces 126 // other standard set types in the code. insert(T * ptr)127 void insert(T* ptr) { add(ptr); } 128 129 // Range insertion. 130 template <typename InputIter> insert(InputIter first,InputIter last)131 void insert(InputIter first, InputIter last) { 132 for (; first != last; ++first) 133 add(*first); 134 } 135 136 // Insert of aitems of |other| into the current set. This 137 // is slightly more efficient than using range insertion 138 // with insert(other.begin(), other.end()). insert(const PointerSet & other)139 void insert(const PointerSet& other) { 140 for (const_iterator iter = other.begin(); iter.valid(); ++iter) 141 add(*iter); 142 } 143 144 // Return a new set that is the intersection of the current one 145 // and |other|. intersection_with(const PointerSet & other)146 PointerSet intersection_with(const PointerSet& other) const { 147 PointerSet result; 148 for (const_iterator iter = other.begin(); iter.valid(); ++iter) { 149 if (contains(*iter)) 150 result.add(*iter); 151 } 152 return result; 153 } 154 155 // Only provide const interators for pointer sets. 156 struct const_iterator : public NodeIterator { 157 T* const* operator->() const { 158 return &const_cast<T*>(static_cast<const T*>(node_->ptr_)); 159 } 160 T* operator*() const { 161 return const_cast<T*>(static_cast<const T*>(node_->ptr_)); 162 } 163 164 // The following allows: 165 // std::vector<T*> vector(set.begin(), set.end()); 166 using iterator_category = std::forward_iterator_tag; 167 using difference_type = std::ptrdiff_t; 168 using value_type = T*; 169 using pointer = T**; 170 using reference = T*&; 171 }; 172 begin()173 const_iterator begin() const { return {NodeBegin()}; } end()174 const_iterator end() const { return {NodeEnd()}; } 175 176 // Only used for unit-tests so performance is not critical. 177 bool operator==(const PointerSet& other) const { 178 if (size() != other.size()) 179 return false; 180 181 for (const_iterator iter = begin(); iter.valid(); ++iter) 182 if (!other.contains(*iter)) 183 return false; 184 185 for (const_iterator iter = other.begin(); iter.valid(); ++iter) 186 if (!contains(*iter)) 187 return false; 188 189 return true; 190 } 191 192 // Convert this to a vector, more convenient and slightly faster than using 193 // std::vector<T*>(set.begin(), set.end()). ToVector()194 std::vector<T*> ToVector() const { 195 std::vector<T*> result(this->size()); 196 auto it_result = result.begin(); 197 for (auto it = this->begin(); it.valid(); ++it) 198 *it_result++ = *it; 199 return result; 200 } 201 202 private: 203 // Lookup node matching a given pointer. If result->valid() is true 204 // then the pointer was found, otherwise, this is the location of 205 // the best place to insert it. Lookup(T * ptr)206 NodeType* Lookup(T* ptr) const { 207 size_t hash = NodeType::MakeHash(ptr); 208 return NodeLookup(hash, [&](NodeType* node) { return node->ptr_ == ptr; }); 209 } 210 }; 211 212 #endif // SRC_GN_POINTER_SET_H_ 213