// SPDX-License-Identifier: GPL-2.0 /* Traditionally, most of our data structures are collected as tables * (=arrays) of pointers. The advantage is that pointers (or references) * to objects are stable, even if the array grows and reallocates. * Implicitly, the table is the owner of the objects and the objets * are deleted (freed) when removed from the table. * This header defines an std::vector<> of unique_ptr<>s to make this * explicit. * * The state I want to reach across the code base: whenever a part of the * code owns a heap allocated object, it *always* possesses a unique_ptr<> * to that object. All naked pointers are invariably considered as * "non owning". * * There are two ways to end ownership: * 1) The std::unique_ptr<> goes out of scope and the object is * automatically deleted. * 2) Ownership is passed to another std::unique_ptr<> using std::move(). * * This means that when adding an object to an owning_table, * ownership of a std::unique_ptr<> is given up with std::move(). * The table then returns a non-owning pointer to the object and * optionally the insertion index. * * In converse, when removing an object, one provides a non-owning * pointer, which is turned into an owning std::unique_ptr<> and * the index where the object was removed. * When ignoring the returned owning pointer, the object is * automatically freed. * * The functions to add an entry to the table are called "put()", * potentially with a suffix. The functions to remove an entry * are called "pull()", likewise with an optional suffix. * * There are two versions of the table: * 1) An unordered version, where the caller is responsible for * adding at specified positions (either given by an index or at the end). * Removal via a non-owning pointer is implemented by a linear search * over the whole table. * 2) An ordered version, where a comparison function that returns -1, 0, 1 * is used to add objects. In that case, the caller must make sure that * no two ojects that compare equal are added to the table. * Obviously, the compare function is supposed to be replaced by the * "spaceship operator" in due course. * Here, adding and removing via non-owning pointers is implemented * using a binary search. * * Note that, since the table contains std::unique_ptr<>s, to loop over * all entries, it is best to use something such as * for (const auto &ptr: table) ... * I plan to add iterator adapters, that autometically dereference * the unique_ptr<>s and provide const-references for const-tables. * * Time will tell how useful this class is. */ #ifndef CORE_OWNING_TABLE_H #define CORE_OWNING_TABLE_H #include "errorhelper.h" #include #include #include #include template class owning_table : public std::vector> { public: struct put_result { T *ptr; size_t idx; }; struct pull_result { std::unique_ptr ptr; size_t idx; }; size_t get_idx(const T *item) const { auto it = std::find_if(this->begin(), this->end(), [item] (auto &i1) {return i1.get() == item; }); return it != this->end() ? it - this->begin() : std::string::npos; } T *put_at(std::unique_ptr item, size_t idx) { T *res = item.get(); insert(this->begin() + idx, std::move(item)); return res; } // Returns index of added item put_result put_back(std::unique_ptr item) { T *ptr = item.get(); push_back(std::move(item)); return { ptr, this->size() - 1 }; } std::unique_ptr pull_at(size_t idx) { auto it = this->begin() + idx; std::unique_ptr res = std::move(*it); this->erase(it); return res; } pull_result pull(const T *item) { size_t idx = get_idx(item); if (idx == std::string::npos) { report_info("Warning: removing unexisting item in %s", __func__); return { std::unique_ptr(), std::string::npos }; } return { pull_at(idx), idx }; } }; // Note: there must not be any elements that compare equal! template class sorted_owning_table : public owning_table { public: using typename owning_table::put_result; using typename owning_table::pull_result; // Returns index of added item put_result put(std::unique_ptr item) { auto it = std::lower_bound(this->begin(), this->end(), item, [] (const auto &i1, const auto &i2) { return CMP(*i1, *i2) < 0; }); if (it != this->end() && CMP(**it, *item) == 0) report_info("Warning: adding duplicate item in %s", __func__); size_t idx = it - this->begin(); T *ptr = item.get(); this->insert(it, std::move(item)); return { ptr, idx }; } // Optimized version of get_idx(), which uses binary search // If not found, fall back to linear search and emit a warning. // Note: this is probaly slower than a linesr search. But for now, // it helps finding consistency problems. size_t get_idx(const T *item) const { auto it = std::lower_bound(this->begin(), this->end(), item, [] (const auto &i1, const auto &i2) { return CMP(*i1, *i2) < 0; }); if (it == this->end() || CMP(**it, *item) != 0) { size_t idx = owning_table::get_idx(item); if (idx != std::string::npos) report_info("Warning: index found by linear but not by binary search in %s", __func__); return idx; } return it - this->begin(); } // Get place where insertion would take place size_t get_insertion_index(const T *item) const { auto it = std::lower_bound(this->begin(), this->end(), item, [] (const auto &i1, const auto &i2) { return CMP(*i1, *i2) < 0; }); return it - this->begin(); } // Note: this is silly - finding the pointer by a linear search // is probably significantly faster than doing a binary search. // But it helps finding consistency problems for now. Remove in // due course. pull_result pull(const T *item) { size_t idx = get_idx(item); if (idx == std::string::npos) { report_info("Warning: removing unexisting item in %s", __func__); return { std::unique_ptr(), std::string::npos }; } return { this->pull_at(idx), idx }; } void sort() { std::sort(this->begin(), this->end(), [](const auto &a, const auto &b) { return CMP(*a, *b) < 0; }); } }; #endif