mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-27 20:58:47 +00:00
b95ac3f79c
This is a humongous commit, because it touches all parts of the code. It removes the last user of our horrible TABLE macros, which simulate std::vector<> in a very clumsy way. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
171 lines
6.1 KiB
C++
171 lines
6.1 KiB
C++
// 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 <algorithm>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
template <typename T>
|
|
class owning_table : public std::vector<std::unique_ptr<T>> {
|
|
public:
|
|
struct put_result {
|
|
T *ptr;
|
|
size_t idx;
|
|
};
|
|
struct pull_result {
|
|
std::unique_ptr<T> 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<T> 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<T> item) {
|
|
T *ptr = item.get();
|
|
push_back(std::move(item));
|
|
return { ptr, this->size() - 1 };
|
|
}
|
|
std::unique_ptr<T> pull_at(size_t idx) {
|
|
auto it = this->begin() + idx;
|
|
std::unique_ptr<T> 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<T>(), std::string::npos };
|
|
}
|
|
return { pull_at(idx), idx };
|
|
}
|
|
};
|
|
|
|
// Note: there must not be any elements that compare equal!
|
|
template <typename T, int (*CMP)(const T &, const T &)>
|
|
class sorted_owning_table : public owning_table<T> {
|
|
public:
|
|
using typename owning_table<T>::put_result;
|
|
using typename owning_table<T>::pull_result;
|
|
// Returns index of added item
|
|
put_result put(std::unique_ptr<T> 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<T>::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<T>(), 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
|